Introduction

This is a class project for the class STAT 380.

The main purpose of this project is to predict the number of future restaurant visitor for a restraunt in Japan.

Later on in the project, we would also join a dataset with weather stations in Japan to see the impact of weather on visitors.

The the first dataset comes from Recruit Restraunt Challenge on Kaggle which has data from Japanese restaurants.

The dataset has the following files:

Preparations

Load libraries

Helper functions

Here we create a use defined fuction for multiple plots and also a helper fuction for binomial confidence interval.

# We created this fuction for plotting multiple plot
# Objects can be passed in the fuction
# The output would be multiple plots
multiple_plot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
  figure <- 
    c(list(...), plotlist)
  num_of_plots = length(figure)
  if (is.null(layout)) {
    layout <- matrix(seq(1, cols * ceiling(num_of_plots/cols)),
                    ncol = cols, nrow = ceiling(num_of_plots/cols))
  }
 if (num_of_plots==1) {
    print(figure[[1]])
  } else {
    grid.newpage()
    pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
    for (i in 1:num_of_plots) {
      matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
      print(figure[[i]], vp = viewport(layout.pos.row = matchidx$row,
                                      layout.pos.col = matchidx$col))
    }
  }
}

Load data

For faster speed up of data reading, we used as.tibble()

rpath <- 
  "recruit-restaurant-visitor-forecasting/"
wpath <- 
  "rrv-weather-data/"
wdpath <- 
  "rrv-weather-data/1-1-16_5-31-17_Weather/"
  
air_reserve <- 
  as.tibble(fread(str_c(rpath,'air_reserve.csv')))
store_ids <- 
  as.tibble(fread(str_c(rpath,'store_id_relation.csv')))
hpg_store <- 
  as.tibble(fread(str_c(rpath,'hpg_store_info.csv')))
air_visits <- 
  as.tibble(fread(str_c(rpath,'air_visit_data.csv')))
air_store <- 
  as.tibble(fread(str_c(rpath,'air_store_info.csv')))
hpg_reserve <- 
  as.tibble(fread(str_c(rpath,'hpg_reserve.csv')))
vacations <- 
  as.tibble(fread(str_c(rpath,'date_info.csv')))
test <- 
  as.tibble(fread(str_c(rpath,'sample_submission.csv')))

Overview:

In the first step, we use the function summary and the function glimpse to overview the data

Air visits

summary(air_visits)
 air_store_id        visit_date           visitors     
 Length:252108      Length:252108      Min.   :  1.00  
 Class :character   Class :character   1st Qu.:  9.00  
 Mode  :character   Mode  :character   Median : 17.00  
                                       Mean   : 20.97  
                                       3rd Qu.: 29.00  
                                       Max.   :877.00  
glimpse(air_visits)
Observations: 252,108
Variables: 3
$ air_store_id <chr> "air_ba937bf13d40fb24", "air_ba937bf13d40fb24", "air_ba937bf13d40fb24", "air_ba937bf13d40fb2…
$ visit_date   <chr> "2016-01-13", "2016-01-14", "2016-01-15", "2016-01-16", "2016-01-18", "2016-01-19", "2016-01…
$ visitors     <int> 25, 32, 29, 22, 6, 9, 31, 21, 18, 26, 21, 11, 24, 21, 26, 6, 18, 12, 45, 15, 19, 15, 32, 3, …
air_visits %>% 
  distinct(air_store_id) %>% 
  nrow()
[1] 829

Air Reserve

summary(air_reserve)
 air_store_id       visit_datetime     reserve_datetime   reserve_visitors 
 Length:92378       Length:92378       Length:92378       Min.   :  1.000  
 Class :character   Class :character   Class :character   1st Qu.:  2.000  
 Mode  :character   Mode  :character   Mode  :character   Median :  3.000  
                                                          Mean   :  4.482  
                                                          3rd Qu.:  5.000  
                                                          Max.   :100.000  
glimpse(air_reserve)
Observations: 92,378
Variables: 4
$ air_store_id     <chr> "air_877f79706adbfb06", "air_db4b38ebe7a7ceff", "air_db4b38ebe7a7ceff", "air_877f79706ad…
$ visit_datetime   <chr> "2016-01-01 19:00:00", "2016-01-01 19:00:00", "2016-01-01 19:00:00", "2016-01-01 20:00:0…
$ reserve_datetime <chr> "2016-01-01 16:00:00", "2016-01-01 19:00:00", "2016-01-01 19:00:00", "2016-01-01 16:00:0…
$ reserve_visitors <int> 1, 3, 6, 2, 5, 2, 4, 2, 2, 2, 3, 3, 2, 6, 7, 41, 13, 2, 3, 2, 2, 3, 2, 2, 6, 2, 2, 4, 8,…
air_reserve %>% 
  distinct(air_store_id) %>% 
  nrow()
[1] 314

HPG Reserve

summary(hpg_reserve)
 hpg_store_id       visit_datetime     reserve_datetime   reserve_visitors 
 Length:2000320     Length:2000320     Length:2000320     Min.   :  1.000  
 Class :character   Class :character   Class :character   1st Qu.:  2.000  
 Mode  :character   Mode  :character   Mode  :character   Median :  3.000  
                                                          Mean   :  5.074  
                                                          3rd Qu.:  6.000  
                                                          Max.   :100.000  
glimpse(hpg_reserve)
Observations: 2,000,320
Variables: 4
$ hpg_store_id     <chr> "hpg_c63f6f42e088e50f", "hpg_dac72789163a3f47", "hpg_c8e24dcf51ca1eb5", "hpg_24bb207e5fd…
$ visit_datetime   <chr> "2016-01-01 11:00:00", "2016-01-01 13:00:00", "2016-01-01 16:00:00", "2016-01-01 17:00:0…
$ reserve_datetime <chr> "2016-01-01 09:00:00", "2016-01-01 06:00:00", "2016-01-01 14:00:00", "2016-01-01 11:00:0…
$ reserve_visitors <int> 1, 3, 2, 5, 13, 2, 2, 2, 2, 6, 2, 2, 2, 2, 5, 4, 2, 4, 5, 2, 5, 4, 5, 3, 2, 4, 4, 2, 2, …
hpg_reserve %>% 
  distinct(hpg_store_id) %>% 
  nrow()
[1] 13325

Air Store

summary(air_store)
 air_store_id       air_genre_name     air_area_name         latitude       longitude    
 Length:829         Length:829         Length:829         Min.   :33.21   Min.   :130.2  
 Class :character   Class :character   Class :character   1st Qu.:34.70   1st Qu.:135.3  
 Mode  :character   Mode  :character   Mode  :character   Median :35.66   Median :139.7  
                                                          Mean   :35.65   Mean   :137.4  
                                                          3rd Qu.:35.69   3rd Qu.:139.8  
                                                          Max.   :44.02   Max.   :144.3  
glimpse(air_store)
Observations: 829
Variables: 5
$ air_store_id   <chr> "air_0f0cdeee6c9bf3d7", "air_7cc17a324ae5c7dc", "air_fee8dcf4d619598e", "air_a17f0778617c7…
$ air_genre_name <chr> "Italian/French", "Italian/French", "Italian/French", "Italian/French", "Italian/French", …
$ air_area_name  <chr> "Hyōgo-ken Kōbe-shi Kumoidōri", "Hyōgo-ken Kōbe-shi Kumoidōri", "Hyōgo-ken Kōbe-shi Kumoid…
$ latitude       <dbl> 34.69512, 34.69512, 34.69512, 34.69512, 35.65807, 35.65807, 35.65807, 35.65807, 35.65807, …
$ longitude      <dbl> 135.1979, 135.1979, 135.1979, 135.1979, 139.7516, 139.7516, 139.7516, 139.7516, 139.7516, …

HPG Store

summary(hpg_store)
 hpg_store_id       hpg_genre_name     hpg_area_name         latitude       longitude    
 Length:4690        Length:4690        Length:4690        Min.   :33.31   Min.   :130.3  
 Class :character   Class :character   Class :character   1st Qu.:34.69   1st Qu.:135.5  
 Mode  :character   Mode  :character   Mode  :character   Median :35.66   Median :139.5  
                                                          Mean   :35.81   Mean   :137.7  
                                                          3rd Qu.:35.70   3rd Qu.:139.7  
                                                          Max.   :43.77   Max.   :143.7  
glimpse(hpg_store)
Observations: 4,690
Variables: 5
$ hpg_store_id   <chr> "hpg_6622b62385aec8bf", "hpg_e9e068dd49c5fa00", "hpg_2976f7acb4b3a3bc", "hpg_e51a522e098f0…
$ hpg_genre_name <chr> "Japanese style", "Japanese style", "Japanese style", "Japanese style", "Japanese style", …
$ hpg_area_name  <chr> "Tōkyō-to Setagaya-ku Taishidō", "Tōkyō-to Setagaya-ku Taishidō", "Tōkyō-to Setagaya-ku Ta…
$ latitude       <dbl> 35.64367, 35.64367, 35.64367, 35.64367, 35.64367, 35.64367, 35.64367, 35.64367, 35.64367, …
$ longitude      <dbl> 139.6682, 139.6682, 139.6682, 139.6682, 139.6682, 139.6682, 139.6682, 139.6682, 139.6682, …

Holidays

summary(vacations)
 calendar_date      day_of_week         holiday_flg    
 Length:517         Length:517         Min.   :0.0000  
 Class :character   Class :character   1st Qu.:0.0000  
 Mode  :character   Mode  :character   Median :0.0000  
                                       Mean   :0.0677  
                                       3rd Qu.:0.0000  
                                       Max.   :1.0000  
glimpse(vacations)
Observations: 517
Variables: 3
$ calendar_date <chr> "2016-01-01", "2016-01-02", "2016-01-03", "2016-01-04", "2016-01-05", "2016-01-06", "2016-0…
$ day_of_week   <chr> "Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Sa…
$ holiday_flg   <int> 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…

Store IDs

summary(store_ids)
 air_store_id       hpg_store_id      
 Length:150         Length:150        
 Class :character   Class :character  
 Mode  :character   Mode  :character  
glimpse(store_ids)
Observations: 150
Variables: 2
$ air_store_id <chr> "air_63b13c56b7201bd9", "air_a24bf50c3e90d583", "air_c7f78b4f3cba33ff", "air_947eb2cae4f3e8f…
$ hpg_store_id <chr> "hpg_4bc649e72e2a239a", "hpg_c34b496d0305a809", "hpg_cd8ae0d9bbd58ff9", "hpg_de24ea49dc25d6b…

Reformating features

For exploration purposes, we need to make some changes to the date format.

air_visits <- air_visits %>%
  mutate(visit_date = ymd(visit_date))
air_store <- air_store %>%
  mutate(air_genre_name = as.factor(air_genre_name),
         air_area_name = as.factor(air_area_name))
hpg_reserve <- hpg_reserve %>%
  mutate(visit_datetime = ymd_hms(visit_datetime),
         reserve_datetime = ymd_hms(reserve_datetime))
vacations <- vacations %>%
  mutate(holiday_flg = as.logical(holiday_flg),
         date = ymd(calendar_date))
hpg_store <- hpg_store %>%
  mutate(hpg_genre_name = as.factor(hpg_genre_name),
         hpg_area_name = as.factor(hpg_area_name))
air_reserve <- air_reserve %>%
  mutate(visit_datetime = ymd_hms(visit_datetime),
         reserve_datetime = ymd_hms(reserve_datetime))

Individual feature visualisations

First, we will examine the distribution of features in a single data file. This initial visualization will be the basis of our analysis.

Air Visits

First, we will sart with the number of visits to the air restaruants.

air_1 <- air_visits %>%
  group_by(visit_date) %>%
  summarise(all_visitors = sum(visitors)) %>%
  ggplot(aes(visit_date, all_visitors)) +
  labs(y = "Visitors", x = "Time") +
  geom_line(col = "red")
air_2 <- air_visits %>%
  ggplot(aes(visitors)) +
  geom_vline(xintercept = 20, color = "yellow") +
  geom_histogram(fill = "red", bins = 30) +
  scale_x_log10()
air_3 <- air_visits %>%
  mutate(wday = wday(visit_date, label = TRUE)) %>%
  group_by(wday) %>%
  summarise(visits = median(visitors)) %>%
  ggplot(aes(wday, visits, fill = wday)) +
  geom_col() + 
  theme(legend.position = "none", 
        axis.text.x  = element_text(angle=45, hjust=1, vjust=0.9)) +
  labs(x = "Day", y = "Median Customers")
air_4 <- air_visits %>%
  mutate(month = month(visit_date, label = TRUE)) %>%
  group_by(month) %>%
  summarise(visits = median(visitors)) %>%
  ggplot(aes(month, visits, fill = month)) +
  geom_col() +
  theme(legend.position = "none") +
  labs(x = "Month of Year", y = "Median Customers")
layout <- matrix(c(1,1,1,1,2,3,4,4),2,4,byrow=TRUE)
multiple_plot(air_1,air_2, air_3, air_4, layout=layout)

As we can see from the plots,

  1. There is some kind of a step structure when we look at the whole time-series. It could be because of upcoming restraunts or could just be a pattern.

  2. Each restaurant serves around 20 people at most every day. Sometimes it goes to 100, and in rare cases it could go to more than 100.

  3. As we expected, the number of visitors on Friday and weekend is the largest, while the number of visitors on Monday and Tuesday is relatively small.

  4. For the whole year, December seems to be the month with most visitors. From March to May there were a lot of people

Air Reservations

Now, we’ll compare the booking data to the actual number of visitors, we’ll start by looking at “air” restaurants.

foo <- air_reserve %>%
  mutate(reserve_date = date(reserve_datetime),
         visit_hour = hour(visit_datetime),
         diff_hour = time_length(visit_datetime - reserve_datetime, unit = "hour"),
         diff_day = time_length(visit_datetime - reserve_datetime, unit = "day"),
         visit_date = date(visit_datetime),
         reserve_wday = wday(reserve_datetime, label = TRUE),
         reserve_hour = hour(reserve_datetime),
         visit_wday = wday(visit_datetime, label = TRUE)
         )
plot_1 <- foo %>%
  group_by(visit_date) %>%
  summarise(all_visitors = sum(reserve_visitors)) %>%
  ggplot(aes(visit_date, all_visitors)) +
  geom_line() +
  labs(x = "'air' visit date")
plot_2 <- foo %>%
  group_by(visit_hour) %>%
  summarise(all_visitors = sum(reserve_visitors)) %>%
  ggplot(aes(visit_hour, all_visitors)) +
  geom_col(fill = "green")
layout <- matrix(c(1,1,2,3),2,2,byrow=TRUE)
multiple_plot(plot_1, plot_2, layout=layout)

We can see from the graph above,

  1. The number of booked through the “air” “system dropped dramatically in 2016. Bookings for that year didn’t increase until the end of the year. Visitor Numbers remained strong in 2017. The decline we saw after the first quarter.

  2. Also, we can see from the data that there are more reservations for dinner from about 6 to 9 p.m.

HPG Reservations

Next, we analyse the hpg data.

foo <- hpg_reserve %>%
  mutate(reserve_date = date(reserve_datetime),
         visit_hour = hour(visit_datetime),
         diff_hour = time_length(visit_datetime - reserve_datetime, unit = "hour"),
         diff_day = time_length(visit_datetime - reserve_datetime, unit = "day"),
         visit_date = date(visit_datetime),
         reserve_wday = wday(reserve_datetime, label = TRUE),
         reserve_hour = hour(reserve_datetime),
         visit_wday = wday(visit_datetime, label = TRUE)
         )
plot_1 <- foo %>%
  group_by(visit_date) %>%
  summarise(all_visitors = sum(reserve_visitors)) %>%
  ggplot(aes(visit_date, all_visitors)) +
  geom_line() +
  labs(x = "'hpg' date")
plot_2 <- foo %>%
  group_by(visit_hour) %>%
  summarise(all_visitors = sum(reserve_visitors)) %>%
  ggplot(aes(visit_hour, all_visitors)) +
  geom_col(fill = "pink")
plot_3 <- foo %>%
  filter(diff_hour < 24*5) %>%
  group_by(diff_hour) %>%
  summarise(all_visitors = sum(reserve_visitors)) %>%
  ggplot(aes(diff_hour, all_visitors)) +
  geom_col(fill = "red") +
  labs(x = "Time for reservation to visit in hours")
layout <- matrix(c(1,1,2,3),2,2,byrow=TRUE)
multiple_plot(plot_1, plot_2, plot_3, layout=layout)

We can see

  1. As we can see from the data of “air”, in December 2016, the number of visits after booking showed an obvious peak, showing a more orderly pattern.

  2. Again, here most reservations are for dinner.

  3. In addition, in the last few hours before the visit, the transaction volume here is not larger than the 24 or 48 hours before the visit.

Air Store

After visualization, let’s look at the spatial information.

This is a fully interactive and scalable map of all the “air” restaurants.

It is from the leaflet package.

leaflet(air_store) %>%
  addTiles() %>%
  addProviderTiles("CartoDB.Positron") %>%
  addMarkers(~longitude, ~latitude,
             popup = ~air_store_id, label = ~air_genre_name,
             clusterOptions = markerClusterOptions())

Next, we are going to plot the number of different types of cuisine with the area that has the most air restaurants:

plot_1 <- air_store %>%
  group_by(air_genre_name) %>%
  count() %>%
  ggplot(aes(reorder(air_genre_name, n, FUN = min), n, fill = air_genre_name)) +
  geom_col() +
  coord_flip() +
  theme(legend.position = "none") +
  labs(x = "Type of cuisine", y = "Number of air restaurants")
plot_2 <- air_store %>%
  group_by(air_area_name) %>%
  count() %>%
  ungroup() %>%
  top_n(15,n) %>%
  ggplot(aes(reorder(air_area_name, n, FUN = min) ,n, fill = air_area_name)) +
  geom_col() +
  theme(legend.position = "none") +
  coord_flip() +
  labs(x = "Top 15 areas", y = "Number of air restaurants")
layout <- matrix(c(1,2),2,1,byrow=TRUE)
multiple_plot(plot_1, plot_2, layout=layout)

We can see,

  1. There the largest number of restraunts is Izakaya, the second largest is a Cafe. The least number of restraunts is “Karoke”, “international” or “Asian”.

  2. Fukuoka has the most “air” restaurants, followed by Tokyo.

HPG Store

Using the same method above, we can make a map of “HPG”:

leaflet(hpg_store) %>%
  addTiles() %>%
  addProviderTiles("CartoDB.Positron") %>%
  addMarkers(~longitude, ~latitude,
             popup = ~hpg_store_id, label = ~hpg_genre_name,
              clusterOptions = markerClusterOptions())

Here is the breakdown of genre and area for the hpg restaurants:

plot_1 <- hpg_store %>%
  group_by(hpg_genre_name) %>%
  count() %>%
  ggplot(aes(reorder(hpg_genre_name, n, FUN = min), n, fill = hpg_genre_name)) +
  geom_col() +
  coord_flip() +
  theme(legend.position = "none") +
  labs(x = "Type of cuisine", y = "Number of hpg restaurants")
plot_2 <- hpg_store %>%
  mutate(area = str_sub(hpg_area_name, 1, 20)) %>%
  group_by(area) %>%
  count() %>%
  ungroup() %>%
  top_n(15,n) %>%
  ggplot(aes(reorder(area, n, FUN = min) ,n, fill = area)) +
  geom_col() +
  theme(legend.position = "none") +
  coord_flip() +
  labs(x = "Top 15 areas", y = "Number of hpg restaurants")
layout <- matrix(c(1,2),1,2,byrow=TRUE)
multiple_plot(plot_1, plot_2, layout=layout)

We can see,

  1. When compared with “air” restaurants, “HPG” contains more types of restaurants, and “Japanese style” seems to contain more specific categories in “air” data.

  2. Tokyo and Osaka again feature prominently in the top 15 cities, as we found in the “air” data.

Feature relations

Visitors per genre

For the first one, we will use the multi-feature space diagram to study the relationship between the types of cuisine and the number of tourists.

foo <- air_visits %>%
  left_join(air_store, by = "air_store_id")
foo %>%
  group_by(visit_date, air_genre_name) %>%
  summarise(mean_visitors = mean(visitors)) %>%
  ungroup() %>%
  ggplot(aes(visit_date, mean_visitors, color = air_genre_name)) +
  geom_line() +
  labs(y = "Average number of visitors to 'air' restaurants", x = "Date") +
  theme(legend.position = "none") +
  scale_y_log10() +
  facet_wrap(~ air_genre_name)

We can see:

  1. In general, the average number of customers per day for each type is 10 to 100. Similarly, in each category, long-term trends look fairly stable. Since the end of 2016, the popularity of “creative cuisine” and “Okonomiyaki” has been on the rise, while the popularity of “Asian cuisine” has been on the decline.

  2. Although “Asian” restaurants are rare, they seem to be popular.

The impact of vacations

Now let’s study the effect of vacations on visitor Numbers:

foo <- air_visits %>%
  mutate(calendar_date = as.character(visit_date)) %>%
  left_join(vacations, by = "calendar_date")
plot_1 <- foo %>%
  ggplot(aes(holiday_flg, visitors, color = holiday_flg)) +
  geom_boxplot() +
  scale_y_log10() +
  theme(legend.position = "none")
plot_2 <- foo %>%
  mutate(wday = wday(date, label = TRUE)) %>%
  group_by(wday, holiday_flg) %>%
  summarise(mean_visitors = mean(visitors)) %>%
  ggplot(aes(wday, mean_visitors, color = holiday_flg)) +
  geom_point(size = 4) +
  theme(legend.position = "none") +
  labs(y = "Average number of visitors")
layout <- matrix(c(1,2),1,2,byrow=TRUE)
multiple_plot(plot_1, plot_2, layout=layout)

We can see:

  1. Overall, vacations had no effect on average visitor Numbers (left graph).

  2. Although holiday on a weekend has little to no impact on the visitor numbers. It even decreases them slightly. This is an interesting phenomenon. (right graph).

Restaurants per area and the effect on visitor numbers

Here is a interesting idea that we want to study with,

If we had the only gourmet bar in the area and it was popular, we’d have hundreds of customers and we wouldn’t worry about losing them. But if there are more than 10 restaurants of the same type on this street, even if we do our best, some customers will go elsewhere. So let’s look at the impact of the number of specific restaurant types in each region on the number of customers

We first outlined the frequency of each region specific type for the two datasets air and HPG store. The size of the dot is proportional to the number of cases:

air_store %>%
  mutate(area = str_sub(air_area_name, 1, 12)) %>%
  ggplot(aes(area, air_genre_name)) +
  geom_count(colour = "green") +
  theme(legend.position = "bottom", axis.text.x  = element_text(angle=45, hjust=1, vjust=0.9))

We can see:

  1. Some areas have a wide variety of restaurants, while others have only one air restaurant.

  2. Similarly, cuisines such as izakaya or cafe are very common, while others can only be found in a few areas.

Graphs of the same type for HPG data look similar, but are busier because of the greater number of types.

hpg_store %>%
  mutate(area = str_sub(hpg_area_name, 1, 10)) %>%
  ggplot(aes(area, hpg_genre_name)) +
  geom_count(colour = "blue") +
  theme(legend.position = "bottom", axis.text.x  = element_text(angle=45, hjust=1, vjust=0.9))

We can see,

  1. There are also busy areas with many restaurants and areas with only a few restaurants.

  2. Both “Japanese cuisine” and “international cuisine” are common and popular. “Entertainment bars” and “udon/buckwheat noodles” are rare, as are “Shanghai cuisine” and “dim sum”.

We start with the air data:

air_store %>%
  group_by(air_genre_name, air_area_name) %>%
  count() %>%
  ggplot(aes(reorder(air_genre_name, n, FUN = mean), n)) +
  geom_boxplot() +
  geom_jitter(color = "blue") +
  scale_y_log10() +
  coord_flip() +
  labs(x = "Air genre", y = "Occurences per air area")

We can see:

  1. Only a few types have a median of more than two restaurants in one area. For instance, “Italian/French” restaurants or “Bar/Cocktail” places, are easier to be found with more than two in the same area.

  2. For most types, the distribution is firmly clustered in each area of 2 cases and scattered toward higher Numbers. The number of “cafes” is highest, with 26 in one area.

  3. Strangely enough, the minimum here is 2, not 1. This means that no “air” restaurant is the only one in any region.

air_store %>%
  filter(air_store_id == "air_b5598d12d1b84890" | air_store_id == "air_bbe1c1a47e09f161")
air_visits %>%
  filter(air_store_id == "air_b5598d12d1b84890" | air_store_id == "air_bbe1c1a47e09f161") %>%
  arrange(visit_date) %>%
  head(10)

Now we look at the same distribution for the HPG restaurants:

foobar <- hpg_store %>%
  group_by(hpg_genre_name, hpg_area_name) %>%
  count()
foobar %>%
  ggplot(aes(reorder(hpg_genre_name, n, FUN = mean), n)) +
  geom_boxplot() +
  geom_jitter(color = "red") +
  scale_y_log10() +
  coord_flip() +
  labs(x = "hpg genre", y = "Cases per hpg area")

We can see:

  1. Here, we obviously have a min of one type per region, and because of the high overall number, there is also more diversity in the median case.

2)The most extreme “genre” is “Japanese style”, with an average of more than 10 restaurants per region.

Using information about the number of types of restraunts in each area, we can now quantify the clustering or “crowding” of the dataset and correlate it with the number of visitors. The next figure first shows the overall distribution of air and HPG data points in the first two figures/

foo <- air_visits %>%
  left_join(air_store, by = "air_store_id")
bar <- air_store %>%
  group_by(air_genre_name, air_area_name) %>%
  count()
foobar <- hpg_store %>%
  group_by(hpg_genre_name, hpg_area_name) %>%
  count()
plot_1 <- bar %>%
  ggplot(aes(n)) +
  geom_histogram(fill = "black", binwidth = 1) +
  labs(x = "Air genres per area")
plot_2 <- foobar %>%
  ggplot(aes(n)) +
  geom_histogram(fill = "red", binwidth = 1) +
  labs(x = "HPG genres per area")
plot_3 <- foo %>%
  group_by(air_genre_name, air_area_name) %>%
  summarise(mean_log_visit = mean(log1p(visitors))) %>%
  left_join(bar, by = c("air_genre_name","air_area_name")) %>%
  group_by(n) %>%
  summarise(mean_mlv = mean(mean_log_visit),
            sd_mlv = sd(mean_log_visit)) %>%
  replace_na(list(sd_mlv = 0)) %>%
  ggplot(aes(n, mean_mlv)) +
  geom_point(color = "black", size = 4) +
  geom_errorbar(aes(ymin = mean_mlv - sd_mlv, ymax = mean_mlv + sd_mlv), width = 0.5, size = 0.7, color = "blue") +
  labs(x = "Cases of identical Air genres per area", y = "Mean +/- SD of\n mean log1p visitors")
layout <- matrix(c(1,2,3,3),2,2,byrow=TRUE)
multiple_plot(plot_1, plot_2, plot_3, layout=layout)

Forecasting methods and examples

Finally, we will run our time series prediction models We learned a lot about datasets and their attributes. The following sections describe the basic forecasting methods.

ARIMA / auto.arima

Autoregressive integrated moving average model is a popular method for forecasting. This model consists of three building blocks, three index p, d, q parameterized as ARIMA(p, d, q)

In this project, we will implement the “auto-arima” tool, that estimates the necessary arima parameters for each individual time series.

Also, we’ll implement the need to use the “ts” tool to convert them into time series objects.

We use the air_store_id* (“air_ba937bf13d40fb24”) as an example.

air_id = "air_ba937bf13d40fb24"

To test our predictions, we will follow the same time frame as our final task (April 23-may 31). Here, we automatically extract the 39 days from the length of the predicted range of test and define it as our “predicted length”.

pred_len <- test %>%
  separate(id, c("air", "store_id", "date"), sep = "_") %>%
  distinct(date) %>%
  nrow()

We selected a “training” sample that predicted the final 39 days. We calculate the top end of our training date and subtract our “predicted length” from this value to define the validation sample at the beginning of March 14. We also created a data set that contains all of visit_date to prepare for many time series that contain gaps.

max_date <- max(air_visits$visit_date)
split_date <- max_date - pred_len
all_visits <- tibble(visit_date = seq(min(air_visits$visit_date), max(air_visits$visit_date), 1))

Next, we extract the time series for a particular air_store_id*.

foo <- air_visits %>%
  filter(air_store_id == air_id)
visits <- foo %>%
  right_join(all_visits, by = "visit_date") %>%
  mutate(visitors = log1p(visitors)) %>%
  replace_na(list(visitors = median(log1p(foo$visitors)))) %>%
  rownames_to_column()

Now, we divide the data into training and testing sets.

visits_train <- visits %>% filter(visit_date <= split_date)
visits_valid <- visits %>% filter(visit_date > split_date)

Now comes the fitting part.

arima.fit <- auto.arima(tsclean(ts(visits_train$visitors, frequency = 7)),
                        stepwise = FALSE, approximation = FALSE)

Using the fitted ARIMA model, we will “forecast” our “predicted length”. At this point we include the confidence interval.

arima_visits <- arima.fit %>% forecast(h = pred_len, level = c(50,95))

Finally, we plot our prediction.

arima_visits %>%
  autoplot +
  geom_line(aes(as.integer(rowname)/7, visitors), 
            data = visits_valid, color = "grey") +
  labs(x = "Time ", y = " Visitors vs auto.arima predictions")

We found that the predictions from the first few days were very consistent.

Prophet

The Prophet forecasting tool is an open-source software released by Facebook’s core data science team. It is a useful tool for both R and Python.

Let’s look at this tool step by step again. We will build on the work of the ARIMA section and will not repeat any of the explanations that can be found earlier.

We will again create a set of training and validation for the same time period as above. The only difference in our ARIMA approach is:

air_id = "air_ba937bf13d40fb24"
pred_len <- test %>%
  separate(id, c("air", "store_id", "date"), sep = "_") %>%
  distinct(date) %>%
  nrow()
max_date <- max(air_visits$visit_date)
split_date <- max_date - pred_len
all_visits <- tibble(visit_date = seq(min(air_visits$visit_date), max(air_visits$visit_date), 1))
foo <- air_visits %>%
  filter(air_store_id == air_id)
visits <- foo %>%
  right_join(all_visits, by = "visit_date") %>%
  mutate(visitors = log1p(visitors)) %>%
  rownames_to_column() %>%
  select(y = visitors,
         ds = visit_date)
visits_train <- visits %>% filter(ds <= split_date)
visits_valid <- visits %>% filter(ds > split_date)

Here we fit the prophet model and make the forecast:

proph <- prophet(visits_train, changepoint.prior.scale=0.5, yearly.seasonality=FALSE)
Disabling daily seasonality. Run prophet with daily.seasonality=TRUE to override this.
future <- make_future_dataframe(proph, periods = pred_len)
fcast <- predict(proph, future)

This is the prophet forecast plot:

plot(proph, fcast)

Observations are represented by black dots, fitting models and predictions by blue lines. In the light blue, we see corresponding uncertainty.

Prophet provides a breakdown graph where we check for additional components to the model: trends, annual seasonality (if any), and weekly cycles:

prophet_plot_components(proph, fcast)

As, We can see:

  1. The weekly change pattern detected by wevin is similar to what we found before. For example, Friday/Saturday has more customers than any other time of the week. But, the difference is,the average number of visitors to Sun is lower than at any other time.

  2. But the long-term trend is different from what we’ve seen before. The previous average behavior was more likely to have risen around December 2016, but in this case, it appears to have occurred in mid-2016.

Weather time series

We will now look at an example of the weather data from a Japan Station. We’ll select the station tokyo__tokyo-kana__tonokyo.csv as is has data for most features but not all. Remember that the individual weather data files are in the folder 1-1-16_5-31-17_Weather.

weather_data <- as.tibble(fread(str_c(wdpath,"tokyo__tokyo-kana__tonokyo.csv")))
summary(weather_data)
 calendar_date      avg_temperature high_temperature low_temperature precipitation     hours_sunlight  
 Length:517         Min.   : 0.70   Min.   : 3.80    Min.   :-2.60   Min.   :  0.000   Min.   : 0.000  
 Class :character   1st Qu.: 8.00   1st Qu.:12.80    1st Qu.: 3.70   1st Qu.:  0.000   1st Qu.: 1.200  
 Mode  :character   Median :14.60   Median :19.40    Median :10.20   Median :  1.000   Median : 5.900  
                    Mean   :14.94   Mean   :19.56    Mean   :11.08   Mean   :  7.750   Mean   : 5.548  
                    3rd Qu.:21.50   3rd Qu.:26.10    3rd Qu.:18.00   3rd Qu.:  7.625   3rd Qu.: 9.000  
                    Max.   :31.90   Max.   :37.70    Max.   :26.00   Max.   :106.500   Max.   :13.400  
                                                                     NA's   :249                       
 solar_radiation deepest_snowfall total_snowfall avg_wind_speed avg_vapor_pressure avg_local_pressure
 Min.   : 1.11   Min.   :0.00     Min.   :6      Min.   :1.20   Min.   : 2.00      Min.   : 983.1    
 1st Qu.: 7.77   1st Qu.:0.75     1st Qu.:6      1st Qu.:2.30   1st Qu.: 6.00      1st Qu.:1006.6    
 Median :12.12   Median :2.00     Median :6      Median :2.80   Median :10.90      Median :1011.6    
 Mean   :13.38   Mean   :2.50     Mean   :6      Mean   :2.87   Mean   :13.06      Mean   :1011.4    
 3rd Qu.:18.18   3rd Qu.:3.75     3rd Qu.:6      3rd Qu.:3.30   3rd Qu.:19.10      3rd Qu.:1016.1    
 Max.   :29.87   Max.   :6.00     Max.   :6      Max.   :7.20   Max.   :32.60      Max.   :1029.2    
                 NA's   :513      NA's   :516                   NA's   :1                            
  avg_humidity    avg_sea_pressure  cloud_cover    
 Min.   : 28.00   Min.   : 985.8   Min.   : 0.000  
 1st Qu.: 53.00   1st Qu.:1009.5   1st Qu.: 4.000  
 Median : 66.00   Median :1014.5   Median : 7.500  
 Mean   : 66.16   Mean   :1014.3   Mean   : 6.721  
 3rd Qu.: 79.00   3rd Qu.:1019.0   3rd Qu.:10.000  
 Max.   :100.00   Max.   :1032.1   Max.   :10.000  
 NA's   :1                                         
glimpse(weather_data)
Observations: 517
Variables: 15
$ calendar_date      <chr> "2016-01-01", "2016-01-02", "2016-01-03", "2016-01-04", "2016-01-05", "2016-01-06", "2…
$ avg_temperature    <dbl> 7.5, 7.3, 9.3, 9.2, 10.9, 8.9, 8.7, 6.8, 7.3, 7.9, 7.1, 3.4, 4.3, 6.0, 5.6, 6.5, 5.8, …
$ high_temperature   <dbl> 12.1, 13.4, 16.2, 15.2, 15.4, 11.3, 12.9, 10.9, 11.6, 12.9, 10.5, 6.1, 9.7, 11.4, 10.9…
$ low_temperature    <dbl> 3.6, 2.6, 3.0, 4.4, 5.1, 7.1, 4.5, 3.1, 2.8, 2.4, 3.8, 0.9, -0.9, 0.2, 2.0, 1.8, 2.3, …
$ precipitation      <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 0.0, 0.0, NA, NA, 0.0, NA, 3.0, 67.0, 0.0, NA,…
$ hours_sunlight     <dbl> 9.0, 7.5, 8.3, 8.9, 8.4, 0.0, 4.8, 8.7, 8.9, 9.1, 3.3, 0.0, 8.7, 9.1, 8.1, 9.1, 3.1, 1…
$ solar_radiation    <dbl> 11.80, 11.59, 10.77, 11.19, 10.57, 4.54, 7.98, 10.99, 11.87, 11.84, 7.85, 1.42, 10.86,…
$ deepest_snowfall   <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 6, 3, 1, NA, NA, N…
$ total_snowfall     <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 6, NA, NA, NA, NA,…
$ avg_wind_speed     <dbl> 2.6, 1.9, 1.4, 1.6, 1.8, 1.9, 2.5, 1.8, 2.1, 2.2, 2.2, 2.1, 1.7, 2.0, 2.5, 1.9, 2.1, 3…
$ avg_vapor_pressure <dbl> 4.5, 5.2, 7.0, 6.8, 6.5, 7.9, 5.4, 4.7, 5.3, 5.3, 5.3, 5.6, 4.9, 4.6, 5.7, 5.1, 5.4, 7…
$ avg_local_pressure <dbl> 1022.4, 1018.6, 1013.2, 1010.4, 1013.1, 1014.0, 1009.4, 1010.4, 1014.1, 1013.5, 1014.1…
$ avg_humidity       <int> 45, 51, 61, 60, 51, 69, 49, 48, 53, 50, 52, 72, 60, 52, 64, 54, 59, 95, 43, 42, 37, 35…
$ avg_sea_pressure   <dbl> 1025.4, 1021.5, 1016.2, 1013.3, 1016.0, 1017.0, 1012.4, 1013.4, 1017.1, 1016.5, 1017.1…
$ cloud_cover        <dbl> 2.5, 4.3, 5.3, 0.0, 2.8, 10.0, 6.8, 3.0, 1.8, 1.8, 8.5, 7.5, 2.5, 0.5, 6.0, 2.3, 8.0, …

As, we can see:

  1. Our train and test sets are covered by daily data for the 517 days. These data include information such as temperature, rain, snow, air pressure, and even cloud cover or sunlight hours.

  2. We see that some features include mostly NA. In fact, this specific weather station data set is one of the more complete ones and you will find many more missing values in other stations. Some stations appear to have essentially complete feature data. This makes it necessary to focus on the overall common features in a first modelling approach.

We will do a little bit of formatting to add some date features like month or day of the week:

weather_data <- weather_data %>%
  mutate(date = ymd(calendar_date),
         wday = wday(date, label = TRUE, abbr = TRUE),
         month = month(date, label = TRUE, abbr = TRUE),
         week = week(date)) %>%
  select(-calendar_date)

The monthly statistics for high temperature and average humidity.

p1 <- weather_data %>%
  ggplot(aes(x = high_temperature, y = fct_rev(month), fill = ..x..)) +
  geom_density_ridges_gradient(scale = 3, rel_min_height = 0.01, gradient_lwd = 1., bandwidth = 1.4) +
  scale_fill_viridis(name = "T_max [°C]", option = "C") +
  ggtitle("Maximum temperature at station \ntokyo tokyo-kana tonokyo in 2016/17") +
  labs(x = "High temperature", y = "") +
  theme_ridges(font_size = 13, grid = TRUE) +
  theme(legend.position = "none") +
  theme(axis.title.y = element_blank())
p2 <- weather_data %>%
  ggplot(aes(x = avg_humidity, y = fct_rev(month), fill = ..x..)) +
  geom_density_ridges_gradient(scale = 3, rel_min_height = 0.01, gradient_lwd = 1., bandwidth = 4) +
  scale_fill_continuous(low = "white", high = "dark blue") +
  ggtitle("Average humidity at station \ntokyo tokyo-kana tonokyo in 2016/17") +
  labs(x = "Humidity", y = "", fill = "Humidity") +
  theme_ridges(font_size = 13, grid = TRUE) +
  theme(legend.position = "none") +
  theme(axis.title.y = element_blank())
layout <- matrix(c(1,2),1,2,byrow=TRUE)
multiplot(p1, p2, layout=layout)

What we find that summers in Tokyo are quite a bit hotter but also more humid. There is a range of about 20 degrees celsius between winter and summer, and a spread of what looks like around 10 degrees within a typical month. The humidity has more variance per month but the difference between winter and summer is still significant.

Other weather information include the precipitation, total and deepest snowfall (all three mostly missing for this station), hours of sunlight, average wind speed, various pressures, cloud cover, and solar radiation. Here we plot some of those features over the months of the year:

p1 <- weather_data %>%
  ggplot(aes(fct_rev(month), hours_sunlight, fill = fct_rev(month))) +
  geom_boxplot() +
  coord_flip() +
  theme(legend.position = "none") +
  labs(x = "Month")
p2 <- weather_data %>%
  ggplot(aes(fct_rev(month), cloud_cover, fill = fct_rev(month))) +
  geom_boxplot() +
  coord_flip() +
  theme(legend.position = "none") +
  labs(x = "Month")
p3 <- weather_data %>%
  ggplot(aes(fct_rev(month), precipitation, fill = fct_rev(month))) +
  geom_boxplot() +
  coord_flip() +
  theme(legend.position = "none") +
  scale_y_log10() +
  labs(x = "Month")
p4 <- weather_data %>%
  ggplot(aes(fct_rev(month), avg_local_pressure, fill = fct_rev(month))) +
  geom_boxplot() +
  coord_flip() +
  theme(legend.position = "none") +
  scale_y_log10() +
  labs(x = "Month")
layout <- matrix(c(1,2,3,4),2,2,byrow=TRUE)
multiplot(p1, p2, p3, p4, layout=layout)

As, We can see:

  1. There are less number of sunlight hours in the month ofSep than in Dec, despite the significantly shorter days. This could be explained by the cloud cover plot that shows that winter is considerably less cloudy than the rest of the year. The cloud cover appears to be measured on a relative scale between 0 and 10.

  2. The overall precipitation seems lowest in Feb; although there is a large variance within a month and all boxes are consistent. Remember that the precipitation feature has many missing values.

3)The average local pressure clearly drops during the summer, with Aug having the lowest pressure values. This is consistent with the tendency for higher precipitation during that month.

WE planned to doibg some further anaylsis on weather data.

Now we are going to do the unsupervised learning for weather_data.

BigCities <- weather_data %>%
  arrange(desc(avg_temperature)) %>%
  head(4000) %>%
  select(high_temperature, low_temperature)
city_clusts <- BigCities %>%
  kmeans(centers = 9) %>%
  fitted("classes") %>%
  as.character()
BigCities <- BigCities %>% mutate(cluster = city_clusts)
BigCities %>% ggplot(aes(x = low_temperature, y = high_temperature)) +
geom_point(aes(color = cluster), alpha = 0.5)

Now, we are going the spread weather_stations data by longitude and latitude.

weather_stations1<-
  weather_stations%>%
  spread(longitude,latitude)
weather_stations1%>%
  head(6)
apply(weather_data,2,class)
   avg_temperature   high_temperature    low_temperature      precipitation     hours_sunlight    solar_radiation 
       "character"        "character"        "character"        "character"        "character"        "character" 
  deepest_snowfall     total_snowfall     avg_wind_speed avg_vapor_pressure avg_local_pressure       avg_humidity 
       "character"        "character"        "character"        "character"        "character"        "character" 
  avg_sea_pressure        cloud_cover               date               wday              month               week 
       "character"        "character"        "character"        "character"        "character"        "character" 

From this one we know the types of every variables in this data set.

sim_mean <- sapply(1:100, function(x) {
  idx <- sample(1:nrow(weather_data),size = 0.8*nrow(weather_data),replace = TRUE)
  mean(weather_data$avg_temperature[idx])
})
sim_mean
  [1] 15.26150 14.82712 14.50460 14.93148 15.37700 15.34988 15.34383 14.14455 14.34310 15.07990 14.65278 14.87603
 [13] 15.38596 14.84262 15.51937 15.27797 14.86513 14.91429 14.45666 15.12785 14.54625 15.02639 15.31404 14.87821
 [25] 14.69177 15.28910 15.64576 14.73293 15.07022 14.67700 15.10291 15.08571 14.99419 14.64891 15.38862 14.67845
 [37] 14.81840 15.17700 14.57046 14.97869 14.69927 14.91961 14.64068 14.92906 14.95666 14.81041 14.49903 14.59128
 [49] 14.55860 15.15351 14.92203 14.98378 15.00412 15.11065 15.14843 14.85424 14.94746 14.31768 14.91090 14.62906
 [61] 14.61840 14.52833 15.08814 14.70630 14.86610 14.98354 14.58039 14.92131 15.20266 14.98184 14.61695 15.06634
 [73] 14.90944 15.03293 15.65521 14.14528 15.06828 14.46707 14.72252 14.40751 15.11768 15.81356 15.05206 15.26005
 [85] 14.92954 14.82107 14.92567 13.96707 14.99952 14.70242 14.96973 15.69831 15.14794 15.23826 15.49782 15.40048
 [97] 15.17748 15.04383 14.96199 14.58354
LS0tCnRpdGxlOiAnUmVzdGF1cmFudCBWaXNpdG9yIFByZWRpY3Rpb24nCmRhdGU6ICdgciBTeXMuRGF0ZSgpYCcKb3V0cHV0OgogICAgaHRtbF9ub3RlYm9vawotLS0KCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRSwgZWNobz1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwgZXJyb3I9RkFMU0UpCmBgYAoKIyBJbnRyb2R1Y3Rpb24KClRoaXMgaXMgYSBjbGFzcyBwcm9qZWN0IGZvciB0aGUgY2xhc3MgU1RBVCAzODAuCgpUaGUgbWFpbiBwdXJwb3NlIG9mIHRoaXMgcHJvamVjdCBpcyB0byBwcmVkaWN0IHRoZSBudW1iZXIgb2YgZnV0dXJlIHJlc3RhdXJhbnQgdmlzaXRvciBmb3IgYSByZXN0cmF1bnQgaW4gSmFwYW4uCgpMYXRlciBvbiBpbiB0aGUgcHJvamVjdCwgd2Ugd291bGQgYWxzbyBqb2luIGEgZGF0YXNldCB3aXRoIHdlYXRoZXIgc3RhdGlvbnMgaW4gSmFwYW4gdG8gc2VlIHRoZSBpbXBhY3Qgb2Ygd2VhdGhlciBvbiB2aXNpdG9ycy4KClRoZSB0aGUgZmlyc3QgZGF0YXNldCBjb21lcyBmcm9tIFJlY3J1aXQgUmVzdHJhdW50IENoYWxsZW5nZSBvbiBLYWdnbGUgd2hpY2ggaGFzIGRhdGEgZnJvbSBKYXBhbmVzZSByZXN0YXVyYW50cy4KClRoZSBkYXRhc2V0IGhhcyB0aGUgZm9sbG93aW5nIGZpbGVzOgoKLSBhaXJfdmlzaXRfZGF0YS5jc3Y6IFRoaXMgZmlsZSBjb250YWlucyB0aGUgdGhlIGFpciByZXN0YXVyYW50cyBoaXN0b3JpY2FsIGRhdGEuIEl0IGlzIHRoZSBwcmltYXJ5IHRyYWluaW5nIGRhdGEgc2V0LgoKLSBhaXJfcmVzZXJ2ZS5jc3YgLyBocGdfcmVzZXJ2ZS5jc3Y6IFRoaXMgZmlsZSBjb250YWlucyB0aGUgZGF0YSBhYm91dCBib29raW5ncyBkb25lIHRocm91Z2ggdGhyb3VnaCB0aGUgYWlyIC8gSFBHIHN5c3RlbXMuCgotIGFpcl9zdG9yZV9pbmZvLmNzdiAvIGhwZ19zdG9yZV9pbmZvLmNzdjogVGhpcyBmaWxlIGNvbnRhaW5zIGluZm9ybWF0aW9uIGFib3V0IGFpciAvIEhQRyByZXN0YXVyYW50cywgaW5jbHVkaW5nIHR5cGUgYW5kIGxvY2F0aW9uLgoKLSBzdG9yZV9pZF9yZWxhdGlvbi5jc3Y6IFRoaXMgZmlsZSBpcyB1c2VkIHRvIGNvbm5lY3QgdGhlIGFpciBhbmQgaHBnIGZpbGVzIGJ5IGpvaW5pbmcgb24gaWRzCgotIGRhdGVfaW5mby5jc3Y6IFRoaXMgZmlsZSBjb250YWlucyBkYXRhIHJlZ2FyZGluZyBKYXBhbmVzZSB2YWNhdGlvbnMuCgoKIyBQcmVwYXJhdGlvbnMgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9CgoKIyMgTG9hZCBsaWJyYXJpZXMKCgpgYGB7ciwgZWNobyA9IEZBTFNFfQojIGRhdGEgbWFuaXB1bGF0aW9uCmxpYnJhcnkoJ2ZvcmNhdHMnKSAKbGlicmFyeSgncmVhZHInKSAKbGlicmFyeSgndGliYmxlJykgCmxpYnJhcnkoJ3RpZHlyJykgCmxpYnJhcnkoJ3N0cmluZ3InKQpsaWJyYXJ5KCdicm9vbScpIApsaWJyYXJ5KCdkcGx5cicpIApsaWJyYXJ5KCdwdXJycicpIApsaWJyYXJ5KCdkYXRhLnRhYmxlJykKbGlicmFyeSgnbGF6eWV2YWwnKSAKCiMgdmlzdWFsaXNhdGlvbgpsaWJyYXJ5KCdnZ3Bsb3QyJykKbGlicmFyeSgnc2NhbGVzJykgCmxpYnJhcnkoJ2dyaWQnKSAKbGlicmFyeSgnZ3JpZEV4dHJhJykgCmxpYnJhcnkoJ1JDb2xvckJyZXdlcicpIApsaWJyYXJ5KCdjb3JycGxvdCcpIApsaWJyYXJ5KCdnZ3JlcGVsJykgCmxpYnJhcnkoJ2dncmlkZ2VzJykgCmxpYnJhcnkoJ2dnRXh0cmEnKSAKbGlicmFyeSgnZ2dmb3JjZScpIApsaWJyYXJ5KCd2aXJpZGlzJykgCgojIExpYnJhcmllcyBmb3IgbG9hZGluZyBNYXBzIC8gZ2Vvc3BhdGlhbCBkYXRhCmxpYnJhcnkoJ21hcHMnKSAKbGlicmFyeSgnbGVhZmxldCcpIApsaWJyYXJ5KCdsZWFmbGV0LmV4dHJhcycpIApsaWJyYXJ5KCdnZW9zcGhlcmUnKQoKIyBQYWNrYWdlcyB0byBoYW5kbGUgRGF0ZXMgcGx1cyBmb3JlY2FzdCBwYWNrYWdlIGZyb20gZmFjZWJvb2sKbGlicmFyeSgndHNlcmllcycpIApsaWJyYXJ5KCdsdWJyaWRhdGUnKQpsaWJyYXJ5KCdwcm9waGV0JykgCmxpYnJhcnkoJ3RpbWVEYXRlJykgCmxpYnJhcnkoJ3RpbWV0aycpIApsaWJyYXJ5KCdmb3JlY2FzdCcpIApgYGAKCiMjIEhlbHBlciBmdW5jdGlvbnMKCkhlcmUgd2UgY3JlYXRlIGEgdXNlIGRlZmluZWQgZnVjdGlvbiBmb3IgbXVsdGlwbGUgcGxvdHMgYW5kIGFsc28gYSBoZWxwZXIgZnVjdGlvbiBmb3IgYmlub21pYWwgY29uZmlkZW5jZSBpbnRlcnZhbC4KCmBgYHtyfQojIFdlIGNyZWF0ZWQgdGhpcyBmdWN0aW9uIGZvciBwbG90dGluZyBtdWx0aXBsZSBwbG90CiMgT2JqZWN0cyBjYW4gYmUgcGFzc2VkIGluIHRoZSBmdWN0aW9uCiMgVGhlIG91dHB1dCB3b3VsZCBiZSBtdWx0aXBsZSBwbG90cwoKbXVsdGlwbGVfcGxvdCA8LSBmdW5jdGlvbiguLi4sIHBsb3RsaXN0PU5VTEwsIGZpbGUsIGNvbHM9MSwgbGF5b3V0PU5VTEwpIHsKICBmaWd1cmUgPC0gCiAgICBjKGxpc3QoLi4uKSwgcGxvdGxpc3QpCiAgbnVtX29mX3Bsb3RzID0gbGVuZ3RoKGZpZ3VyZSkKICBpZiAoaXMubnVsbChsYXlvdXQpKSB7CiAgICBsYXlvdXQgPC0gbWF0cml4KHNlcSgxLCBjb2xzICogY2VpbGluZyhudW1fb2ZfcGxvdHMvY29scykpLAogICAgICAgICAgICAgICAgICAgIG5jb2wgPSBjb2xzLCBucm93ID0gY2VpbGluZyhudW1fb2ZfcGxvdHMvY29scykpCiAgfQogaWYgKG51bV9vZl9wbG90cz09MSkgewogICAgcHJpbnQoZmlndXJlW1sxXV0pCgogIH0gZWxzZSB7CiAgICBncmlkLm5ld3BhZ2UoKQogICAgcHVzaFZpZXdwb3J0KHZpZXdwb3J0KGxheW91dCA9IGdyaWQubGF5b3V0KG5yb3cobGF5b3V0KSwgbmNvbChsYXlvdXQpKSkpCiAgICBmb3IgKGkgaW4gMTpudW1fb2ZfcGxvdHMpIHsKICAgICAgbWF0Y2hpZHggPC0gYXMuZGF0YS5mcmFtZSh3aGljaChsYXlvdXQgPT0gaSwgYXJyLmluZCA9IFRSVUUpKQogICAgICBwcmludChmaWd1cmVbW2ldXSwgdnAgPSB2aWV3cG9ydChsYXlvdXQucG9zLnJvdyA9IG1hdGNoaWR4JHJvdywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXlvdXQucG9zLmNvbCA9IG1hdGNoaWR4JGNvbCkpCiAgICB9CiAgfQp9CmBgYAoKCiMjIExvYWQgZGF0YQoKRm9yIGZhc3RlciBzcGVlZCB1cCBvZiBkYXRhIHJlYWRpbmcsIHdlIHVzZWQgYXMudGliYmxlKCkKCmBgYHtyIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9RkFMU0V9CnJwYXRoIDwtIAogICJyZWNydWl0LXJlc3RhdXJhbnQtdmlzaXRvci1mb3JlY2FzdGluZy8iCndwYXRoIDwtIAogICJycnYtd2VhdGhlci1kYXRhLyIKd2RwYXRoIDwtIAogICJycnYtd2VhdGhlci1kYXRhLzEtMS0xNl81LTMxLTE3X1dlYXRoZXIvIgogIAphaXJfcmVzZXJ2ZSA8LSAKICBhcy50aWJibGUoZnJlYWQoc3RyX2MocnBhdGgsJ2Fpcl9yZXNlcnZlLmNzdicpKSkKc3RvcmVfaWRzIDwtIAogIGFzLnRpYmJsZShmcmVhZChzdHJfYyhycGF0aCwnc3RvcmVfaWRfcmVsYXRpb24uY3N2JykpKQpocGdfc3RvcmUgPC0gCiAgYXMudGliYmxlKGZyZWFkKHN0cl9jKHJwYXRoLCdocGdfc3RvcmVfaW5mby5jc3YnKSkpCmFpcl92aXNpdHMgPC0gCiAgYXMudGliYmxlKGZyZWFkKHN0cl9jKHJwYXRoLCdhaXJfdmlzaXRfZGF0YS5jc3YnKSkpCmFpcl9zdG9yZSA8LSAKICBhcy50aWJibGUoZnJlYWQoc3RyX2MocnBhdGgsJ2Fpcl9zdG9yZV9pbmZvLmNzdicpKSkKaHBnX3Jlc2VydmUgPC0gCiAgYXMudGliYmxlKGZyZWFkKHN0cl9jKHJwYXRoLCdocGdfcmVzZXJ2ZS5jc3YnKSkpCnZhY2F0aW9ucyA8LSAKICBhcy50aWJibGUoZnJlYWQoc3RyX2MocnBhdGgsJ2RhdGVfaW5mby5jc3YnKSkpCnRlc3QgPC0gCiAgYXMudGliYmxlKGZyZWFkKHN0cl9jKHJwYXRoLCdzYW1wbGVfc3VibWlzc2lvbi5jc3YnKSkpCmBgYAoKCiMgT3ZlcnZpZXc6IAoKSW4gdGhlIGZpcnN0IHN0ZXAsIHdlIHVzZSB0aGUgZnVuY3Rpb24gc3VtbWFyeSBhbmQgdGhlIGZ1bmN0aW9uIGdsaW1wc2UgdG8gb3ZlcnZpZXcgdGhlIGRhdGEKCiMjIEFpciB2aXNpdHMKCmBgYHtyfQpzdW1tYXJ5KGFpcl92aXNpdHMpCmdsaW1wc2UoYWlyX3Zpc2l0cykKYWlyX3Zpc2l0cyAlPiUgCiAgZGlzdGluY3QoYWlyX3N0b3JlX2lkKSAlPiUgCiAgbnJvdygpCmBgYAoKIyMgQWlyIFJlc2VydmUKCmBgYHtyfQpzdW1tYXJ5KGFpcl9yZXNlcnZlKQpnbGltcHNlKGFpcl9yZXNlcnZlKQphaXJfcmVzZXJ2ZSAlPiUgCiAgZGlzdGluY3QoYWlyX3N0b3JlX2lkKSAlPiUgCiAgbnJvdygpCmBgYAoKIyMgSFBHIFJlc2VydmUKCmBgYHtyfQpzdW1tYXJ5KGhwZ19yZXNlcnZlKQpnbGltcHNlKGhwZ19yZXNlcnZlKQpocGdfcmVzZXJ2ZSAlPiUgCiAgZGlzdGluY3QoaHBnX3N0b3JlX2lkKSAlPiUgCiAgbnJvdygpCmBgYAoKCiMjIEFpciBTdG9yZQoKYGBge3J9CnN1bW1hcnkoYWlyX3N0b3JlKQpnbGltcHNlKGFpcl9zdG9yZSkKYGBgCgojIyBIUEcgU3RvcmUKCmBgYHtyfQpzdW1tYXJ5KGhwZ19zdG9yZSkKZ2xpbXBzZShocGdfc3RvcmUpCmBgYAoKCiMjIEhvbGlkYXlzCgpgYGB7cn0Kc3VtbWFyeSh2YWNhdGlvbnMpCmdsaW1wc2UodmFjYXRpb25zKQpgYGAKCiMjIFN0b3JlIElEcwoKYGBge3J9CnN1bW1hcnkoc3RvcmVfaWRzKQpnbGltcHNlKHN0b3JlX2lkcykKYGBgCgojIyBSZWZvcm1hdGluZyBmZWF0dXJlcwoKRm9yIGV4cGxvcmF0aW9uIHB1cnBvc2VzLCB3ZSBuZWVkIHRvIG1ha2Ugc29tZSBjaGFuZ2VzIHRvIHRoZSBkYXRlIGZvcm1hdC4KCmBgYHtyfQphaXJfdmlzaXRzIDwtIGFpcl92aXNpdHMgJT4lCiAgbXV0YXRlKHZpc2l0X2RhdGUgPSB5bWQodmlzaXRfZGF0ZSkpCgphaXJfc3RvcmUgPC0gYWlyX3N0b3JlICU+JQogIG11dGF0ZShhaXJfZ2VucmVfbmFtZSA9IGFzLmZhY3RvcihhaXJfZ2VucmVfbmFtZSksCiAgICAgICAgIGFpcl9hcmVhX25hbWUgPSBhcy5mYWN0b3IoYWlyX2FyZWFfbmFtZSkpCgpocGdfcmVzZXJ2ZSA8LSBocGdfcmVzZXJ2ZSAlPiUKICBtdXRhdGUodmlzaXRfZGF0ZXRpbWUgPSB5bWRfaG1zKHZpc2l0X2RhdGV0aW1lKSwKICAgICAgICAgcmVzZXJ2ZV9kYXRldGltZSA9IHltZF9obXMocmVzZXJ2ZV9kYXRldGltZSkpCgoKdmFjYXRpb25zIDwtIHZhY2F0aW9ucyAlPiUKICBtdXRhdGUoaG9saWRheV9mbGcgPSBhcy5sb2dpY2FsKGhvbGlkYXlfZmxnKSwKICAgICAgICAgZGF0ZSA9IHltZChjYWxlbmRhcl9kYXRlKSkKCmhwZ19zdG9yZSA8LSBocGdfc3RvcmUgJT4lCiAgbXV0YXRlKGhwZ19nZW5yZV9uYW1lID0gYXMuZmFjdG9yKGhwZ19nZW5yZV9uYW1lKSwKICAgICAgICAgaHBnX2FyZWFfbmFtZSA9IGFzLmZhY3RvcihocGdfYXJlYV9uYW1lKSkKCgphaXJfcmVzZXJ2ZSA8LSBhaXJfcmVzZXJ2ZSAlPiUKICBtdXRhdGUodmlzaXRfZGF0ZXRpbWUgPSB5bWRfaG1zKHZpc2l0X2RhdGV0aW1lKSwKICAgICAgICAgcmVzZXJ2ZV9kYXRldGltZSA9IHltZF9obXMocmVzZXJ2ZV9kYXRldGltZSkpCmBgYAoKCiMgSW5kaXZpZHVhbCBmZWF0dXJlIHZpc3VhbGlzYXRpb25zCgpGaXJzdCwgd2Ugd2lsbCBleGFtaW5lIHRoZSBkaXN0cmlidXRpb24gb2YgZmVhdHVyZXMgaW4gYSBzaW5nbGUgZGF0YSBmaWxlLiBUaGlzIGluaXRpYWwgdmlzdWFsaXphdGlvbiB3aWxsIGJlIHRoZSBiYXNpcyBvZiBvdXIgYW5hbHlzaXMuCgojIyBBaXIgVmlzaXRzCkZpcnN0LCB3ZSB3aWxsIHNhcnQgd2l0aCB0aGUgbnVtYmVyIG9mIHZpc2l0cyB0byB0aGUgYWlyIHJlc3RhcnVhbnRzLgoKYGBge3Igc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiAxIiwgb3V0LndpZHRoPSIxMDAlIn0KYWlyXzEgPC0gYWlyX3Zpc2l0cyAlPiUKICBncm91cF9ieSh2aXNpdF9kYXRlKSAlPiUKICBzdW1tYXJpc2UoYWxsX3Zpc2l0b3JzID0gc3VtKHZpc2l0b3JzKSkgJT4lCiAgZ2dwbG90KGFlcyh2aXNpdF9kYXRlLCBhbGxfdmlzaXRvcnMpKSArCiAgbGFicyh5ID0gIlZpc2l0b3JzIiwgeCA9ICJUaW1lIikgKwogIGdlb21fbGluZShjb2wgPSAicmVkIikKCmFpcl8yIDwtIGFpcl92aXNpdHMgJT4lCiAgZ2dwbG90KGFlcyh2aXNpdG9ycykpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAyMCwgY29sb3IgPSAieWVsbG93IikgKwogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAicmVkIiwgYmlucyA9IDMwKSArCiAgc2NhbGVfeF9sb2cxMCgpCgphaXJfMyA8LSBhaXJfdmlzaXRzICU+JQogIG11dGF0ZSh3ZGF5ID0gd2RheSh2aXNpdF9kYXRlLCBsYWJlbCA9IFRSVUUpKSAlPiUKICBncm91cF9ieSh3ZGF5KSAlPiUKICBzdW1tYXJpc2UodmlzaXRzID0gbWVkaWFuKHZpc2l0b3JzKSkgJT4lCiAgZ2dwbG90KGFlcyh3ZGF5LCB2aXNpdHMsIGZpbGwgPSB3ZGF5KSkgKwogIGdlb21fY29sKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIAogICAgICAgIGF4aXMudGV4dC54ICA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSwgaGp1c3Q9MSwgdmp1c3Q9MC45KSkgKwogIGxhYnMoeCA9ICJEYXkiLCB5ID0gIk1lZGlhbiBDdXN0b21lcnMiKQoKYWlyXzQgPC0gYWlyX3Zpc2l0cyAlPiUKICBtdXRhdGUobW9udGggPSBtb250aCh2aXNpdF9kYXRlLCBsYWJlbCA9IFRSVUUpKSAlPiUKICBncm91cF9ieShtb250aCkgJT4lCiAgc3VtbWFyaXNlKHZpc2l0cyA9IG1lZGlhbih2aXNpdG9ycykpICU+JQogIGdncGxvdChhZXMobW9udGgsIHZpc2l0cywgZmlsbCA9IG1vbnRoKSkgKwogIGdlb21fY29sKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGxhYnMoeCA9ICJNb250aCBvZiBZZWFyIiwgeSA9ICJNZWRpYW4gQ3VzdG9tZXJzIikKCmxheW91dCA8LSBtYXRyaXgoYygxLDEsMSwxLDIsMyw0LDQpLDIsNCxieXJvdz1UUlVFKQptdWx0aXBsZV9wbG90KGFpcl8xLGFpcl8yLCBhaXJfMywgYWlyXzQsIGxheW91dD1sYXlvdXQpCmBgYAoKQXMgd2UgY2FuIHNlZSBmcm9tIHRoZSBwbG90cywKCjEpIFRoZXJlIGlzIHNvbWUga2luZCBvZiBhIHN0ZXAgc3RydWN0dXJlIHdoZW4gd2UgbG9vayBhdCB0aGUgd2hvbGUgdGltZS1zZXJpZXMuIEl0IGNvdWxkIGJlIGJlY2F1c2Ugb2YgdXBjb21pbmcgcmVzdHJhdW50cyBvciBjb3VsZCBqdXN0IGJlIGEgcGF0dGVybi4gCgoyKSBFYWNoIHJlc3RhdXJhbnQgc2VydmVzIGFyb3VuZCAyMCBwZW9wbGUgYXQgbW9zdCBldmVyeSBkYXkuIFNvbWV0aW1lcyBpdCBnb2VzIHRvIDEwMCwgYW5kIGluIHJhcmUgY2FzZXMgaXQgY291bGQgZ28gdG8gbW9yZSB0aGFuIDEwMC4KCjMpIEFzIHdlIGV4cGVjdGVkLCB0aGUgbnVtYmVyIG9mIHZpc2l0b3JzIG9uIEZyaWRheSBhbmQgd2Vla2VuZCBpcyB0aGUgbGFyZ2VzdCwgd2hpbGUgdGhlIG51bWJlciBvZiB2aXNpdG9ycyBvbiBNb25kYXkgYW5kIFR1ZXNkYXkgaXMgcmVsYXRpdmVseSBzbWFsbC4KCjQpIEZvciB0aGUgd2hvbGUgeWVhciwgRGVjZW1iZXIgc2VlbXMgdG8gYmUgdGhlIG1vbnRoIHdpdGggbW9zdCB2aXNpdG9ycy4gCkZyb20gTWFyY2ggdG8gTWF5IHRoZXJlIHdlcmUgYSBsb3Qgb2YgcGVvcGxlCgojIyBBaXIgUmVzZXJ2YXRpb25zCgpOb3csIHdlJ2xsIGNvbXBhcmUgdGhlIGJvb2tpbmcgZGF0YSB0byB0aGUgYWN0dWFsIG51bWJlciBvZiB2aXNpdG9ycywgd2UnbGwgc3RhcnQgYnkgbG9va2luZyBhdCAiYWlyIiByZXN0YXVyYW50cy4gCgpgYGB7ciBzcGxpdD1GQUxTRSwgZmlnLmFsaWduID0gJ2RlZmF1bHQnLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5jYXAgPSJGaWcuIDMiLCBvdXQud2lkdGg9IjEwMCUifQpmb28gPC0gYWlyX3Jlc2VydmUgJT4lCiAgbXV0YXRlKHJlc2VydmVfZGF0ZSA9IGRhdGUocmVzZXJ2ZV9kYXRldGltZSksCiAgICAgICAgIHZpc2l0X2hvdXIgPSBob3VyKHZpc2l0X2RhdGV0aW1lKSwKICAgICAgICAgZGlmZl9ob3VyID0gdGltZV9sZW5ndGgodmlzaXRfZGF0ZXRpbWUgLSByZXNlcnZlX2RhdGV0aW1lLCB1bml0ID0gImhvdXIiKSwKICAgICAgICAgZGlmZl9kYXkgPSB0aW1lX2xlbmd0aCh2aXNpdF9kYXRldGltZSAtIHJlc2VydmVfZGF0ZXRpbWUsIHVuaXQgPSAiZGF5IiksCiAgICAgICAgIHZpc2l0X2RhdGUgPSBkYXRlKHZpc2l0X2RhdGV0aW1lKSwKICAgICAgICAgcmVzZXJ2ZV93ZGF5ID0gd2RheShyZXNlcnZlX2RhdGV0aW1lLCBsYWJlbCA9IFRSVUUpLAogICAgICAgICByZXNlcnZlX2hvdXIgPSBob3VyKHJlc2VydmVfZGF0ZXRpbWUpLAogICAgICAgICB2aXNpdF93ZGF5ID0gd2RheSh2aXNpdF9kYXRldGltZSwgbGFiZWwgPSBUUlVFKQogICAgICAgICApCgpwbG90XzEgPC0gZm9vICU+JQogIGdyb3VwX2J5KHZpc2l0X2RhdGUpICU+JQogIHN1bW1hcmlzZShhbGxfdmlzaXRvcnMgPSBzdW0ocmVzZXJ2ZV92aXNpdG9ycykpICU+JQogIGdncGxvdChhZXModmlzaXRfZGF0ZSwgYWxsX3Zpc2l0b3JzKSkgKwogIGdlb21fbGluZSgpICsKICBsYWJzKHggPSAiJ2FpcicgdmlzaXQgZGF0ZSIpCgpwbG90XzIgPC0gZm9vICU+JQogIGdyb3VwX2J5KHZpc2l0X2hvdXIpICU+JQogIHN1bW1hcmlzZShhbGxfdmlzaXRvcnMgPSBzdW0ocmVzZXJ2ZV92aXNpdG9ycykpICU+JQogIGdncGxvdChhZXModmlzaXRfaG91ciwgYWxsX3Zpc2l0b3JzKSkgKwogIGdlb21fY29sKGZpbGwgPSAiZ3JlZW4iKQoKbGF5b3V0IDwtIG1hdHJpeChjKDEsMSwyLDMpLDIsMixieXJvdz1UUlVFKQptdWx0aXBsZV9wbG90KHBsb3RfMSwgcGxvdF8yLCBsYXlvdXQ9bGF5b3V0KQpgYGAKCldlIGNhbiBzZWUgZnJvbSB0aGUgZ3JhcGggYWJvdmUsCgoxKSBUaGUgbnVtYmVyIG9mIGJvb2tlZCB0aHJvdWdoIHRoZSAiYWlyIiAic3lzdGVtIGRyb3BwZWQgZHJhbWF0aWNhbGx5IGluIDIwMTYuIEJvb2tpbmdzIGZvciB0aGF0IHllYXIgZGlkbid0IGluY3JlYXNlIHVudGlsIHRoZSBlbmQgb2YgdGhlIHllYXIuIFZpc2l0b3IgTnVtYmVycyByZW1haW5lZCBzdHJvbmcgaW4gMjAxNy4gVGhlIGRlY2xpbmUgd2Ugc2F3IGFmdGVyIHRoZSBmaXJzdCBxdWFydGVyLgoKMikgQWxzbywgd2UgY2FuIHNlZSBmcm9tIHRoZSBkYXRhIHRoYXQgdGhlcmUgYXJlICBtb3JlIHJlc2VydmF0aW9ucyBmb3IgZGlubmVyIGZyb20gYWJvdXQgNiB0byA5IHAubS4KCiMjIEhQRyBSZXNlcnZhdGlvbnMKCk5leHQsIHdlIGFuYWx5c2UgdGhlIGhwZyBkYXRhLgoKYGBge3Igc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiA0Iiwgb3V0LndpZHRoPSIxMDAlIn0KZm9vIDwtIGhwZ19yZXNlcnZlICU+JQogIG11dGF0ZShyZXNlcnZlX2RhdGUgPSBkYXRlKHJlc2VydmVfZGF0ZXRpbWUpLAogICAgICAgICB2aXNpdF9ob3VyID0gaG91cih2aXNpdF9kYXRldGltZSksCiAgICAgICAgIGRpZmZfaG91ciA9IHRpbWVfbGVuZ3RoKHZpc2l0X2RhdGV0aW1lIC0gcmVzZXJ2ZV9kYXRldGltZSwgdW5pdCA9ICJob3VyIiksCiAgICAgICAgIGRpZmZfZGF5ID0gdGltZV9sZW5ndGgodmlzaXRfZGF0ZXRpbWUgLSByZXNlcnZlX2RhdGV0aW1lLCB1bml0ID0gImRheSIpLAogICAgICAgICB2aXNpdF9kYXRlID0gZGF0ZSh2aXNpdF9kYXRldGltZSksCiAgICAgICAgIHJlc2VydmVfd2RheSA9IHdkYXkocmVzZXJ2ZV9kYXRldGltZSwgbGFiZWwgPSBUUlVFKSwKICAgICAgICAgcmVzZXJ2ZV9ob3VyID0gaG91cihyZXNlcnZlX2RhdGV0aW1lKSwKICAgICAgICAgdmlzaXRfd2RheSA9IHdkYXkodmlzaXRfZGF0ZXRpbWUsIGxhYmVsID0gVFJVRSkKICAgICAgICAgKQoKcGxvdF8xIDwtIGZvbyAlPiUKICBncm91cF9ieSh2aXNpdF9kYXRlKSAlPiUKICBzdW1tYXJpc2UoYWxsX3Zpc2l0b3JzID0gc3VtKHJlc2VydmVfdmlzaXRvcnMpKSAlPiUKICBnZ3Bsb3QoYWVzKHZpc2l0X2RhdGUsIGFsbF92aXNpdG9ycykpICsKICBnZW9tX2xpbmUoKSArCiAgbGFicyh4ID0gIidocGcnIGRhdGUiKQoKcGxvdF8yIDwtIGZvbyAlPiUKICBncm91cF9ieSh2aXNpdF9ob3VyKSAlPiUKICBzdW1tYXJpc2UoYWxsX3Zpc2l0b3JzID0gc3VtKHJlc2VydmVfdmlzaXRvcnMpKSAlPiUKICBnZ3Bsb3QoYWVzKHZpc2l0X2hvdXIsIGFsbF92aXNpdG9ycykpICsKICBnZW9tX2NvbChmaWxsID0gInBpbmsiKQoKcGxvdF8zIDwtIGZvbyAlPiUKICBmaWx0ZXIoZGlmZl9ob3VyIDwgMjQqNSkgJT4lCiAgZ3JvdXBfYnkoZGlmZl9ob3VyKSAlPiUKICBzdW1tYXJpc2UoYWxsX3Zpc2l0b3JzID0gc3VtKHJlc2VydmVfdmlzaXRvcnMpKSAlPiUKICBnZ3Bsb3QoYWVzKGRpZmZfaG91ciwgYWxsX3Zpc2l0b3JzKSkgKwogIGdlb21fY29sKGZpbGwgPSAicmVkIikgKwogIGxhYnMoeCA9ICJUaW1lIGZvciByZXNlcnZhdGlvbiB0byB2aXNpdCBpbiBob3VycyIpCgpsYXlvdXQgPC0gbWF0cml4KGMoMSwxLDIsMyksMiwyLGJ5cm93PVRSVUUpCm11bHRpcGxlX3Bsb3QocGxvdF8xLCBwbG90XzIsIHBsb3RfMywgbGF5b3V0PWxheW91dCkKYGBgCgpXZSBjYW4gc2VlCgoxKSBBcyB3ZSBjYW4gc2VlIGZyb20gdGhlIGRhdGEgb2YgImFpciIsIGluIERlY2VtYmVyIDIwMTYsIHRoZSBudW1iZXIgb2YgdmlzaXRzIGFmdGVyIGJvb2tpbmcgc2hvd2VkIGFuIG9idmlvdXMgcGVhaywgc2hvd2luZyBhIG1vcmUgb3JkZXJseSBwYXR0ZXJuLgoKMikgQWdhaW4sIGhlcmUgbW9zdCByZXNlcnZhdGlvbnMgYXJlIGZvciBkaW5uZXIuCgozKSBJbiBhZGRpdGlvbiwgaW4gdGhlIGxhc3QgZmV3IGhvdXJzIGJlZm9yZSB0aGUgdmlzaXQsIHRoZSB0cmFuc2FjdGlvbiB2b2x1bWUgaGVyZSBpcyBub3QgbGFyZ2VyIHRoYW4gdGhlIDI0IG9yIDQ4IGhvdXJzIGJlZm9yZSB0aGUgdmlzaXQuCgoKIyMgQWlyIFN0b3JlCgpBZnRlciB2aXN1YWxpemF0aW9uLCBsZXQncyBsb29rIGF0IHRoZSBzcGF0aWFsIGluZm9ybWF0aW9uLgoKVGhpcyBpcyBhIGZ1bGx5IGludGVyYWN0aXZlIGFuZCBzY2FsYWJsZSBtYXAgb2YgYWxsIHRoZSAiYWlyIiByZXN0YXVyYW50cy4gCgpJdCBpcyBmcm9tIHRoZSBsZWFmbGV0IHBhY2thZ2UuCgpgYGB7ciBzcGxpdD1GQUxTRSwgZmlnLmFsaWduID0gJ2RlZmF1bHQnLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5jYXAgPSJGaWcuIDUiLCBvdXQud2lkdGg9IjEwMCUifQpsZWFmbGV0KGFpcl9zdG9yZSkgJT4lCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKCJDYXJ0b0RCLlBvc2l0cm9uIikgJT4lCiAgYWRkTWFya2Vycyh+bG9uZ2l0dWRlLCB+bGF0aXR1ZGUsCiAgICAgICAgICAgICBwb3B1cCA9IH5haXJfc3RvcmVfaWQsIGxhYmVsID0gfmFpcl9nZW5yZV9uYW1lLAogICAgICAgICAgICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpKQpgYGAKCgpOZXh0LCB3ZSBhcmUgZ29pbmcgdG8gcGxvdCB0aGUgbnVtYmVyIG9mIGRpZmZlcmVudCB0eXBlcyBvZiBjdWlzaW5lIHdpdGggdGhlIGFyZWEgdGhhdCBoYXMgdGhlIG1vc3QgYWlyIHJlc3RhdXJhbnRzOgoKCmBgYHtyIHNwbGl0PUZBTFNFLCBmaWcuYWxpZ24gPSAnZGVmYXVsdCcsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLmNhcCA9IkZpZy4gNiIsIG91dC53aWR0aD0iMTAwJSJ9CnBsb3RfMSA8LSBhaXJfc3RvcmUgJT4lCiAgZ3JvdXBfYnkoYWlyX2dlbnJlX25hbWUpICU+JQogIGNvdW50KCkgJT4lCiAgZ2dwbG90KGFlcyhyZW9yZGVyKGFpcl9nZW5yZV9uYW1lLCBuLCBGVU4gPSBtaW4pLCBuLCBmaWxsID0gYWlyX2dlbnJlX25hbWUpKSArCiAgZ2VvbV9jb2woKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBsYWJzKHggPSAiVHlwZSBvZiBjdWlzaW5lIiwgeSA9ICJOdW1iZXIgb2YgYWlyIHJlc3RhdXJhbnRzIikKCnBsb3RfMiA8LSBhaXJfc3RvcmUgJT4lCiAgZ3JvdXBfYnkoYWlyX2FyZWFfbmFtZSkgJT4lCiAgY291bnQoKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgdG9wX24oMTUsbikgJT4lCiAgZ2dwbG90KGFlcyhyZW9yZGVyKGFpcl9hcmVhX25hbWUsIG4sIEZVTiA9IG1pbikgLG4sIGZpbGwgPSBhaXJfYXJlYV9uYW1lKSkgKwogIGdlb21fY29sKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gIlRvcCAxNSBhcmVhcyIsIHkgPSAiTnVtYmVyIG9mIGFpciByZXN0YXVyYW50cyIpCgpsYXlvdXQgPC0gbWF0cml4KGMoMSwyKSwyLDEsYnlyb3c9VFJVRSkKbXVsdGlwbGVfcGxvdChwbG90XzEsIHBsb3RfMiwgbGF5b3V0PWxheW91dCkKYGBgCgpXZSBjYW4gc2VlLAoKMSkgVGhlcmUgdGhlIGxhcmdlc3QgbnVtYmVyIG9mIHJlc3RyYXVudHMgaXMgSXpha2F5YSwgdGhlIHNlY29uZCBsYXJnZXN0IGlzIGEgQ2FmZS4gVGhlIGxlYXN0IG51bWJlciBvZiByZXN0cmF1bnRzIGlzICJLYXJva2UiLCAiaW50ZXJuYXRpb25hbCIgb3IgIkFzaWFuIi4KCjIpIEZ1a3Vva2EgaGFzIHRoZSBtb3N0ICJhaXIiIHJlc3RhdXJhbnRzLCBmb2xsb3dlZCBieSBUb2t5by4KCiMjIEhQRyBTdG9yZQpVc2luZyB0aGUgc2FtZSBtZXRob2QgYWJvdmUsIHdlIGNhbiBtYWtlIGEgbWFwIG9mICJIUEciOgoKYGBge3Igc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiA3Iiwgb3V0LndpZHRoPSIxMDAlIn0KbGVhZmxldChocGdfc3RvcmUpICU+JQogIGFkZFRpbGVzKCkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JQogIGFkZE1hcmtlcnMofmxvbmdpdHVkZSwgfmxhdGl0dWRlLAogICAgICAgICAgICAgcG9wdXAgPSB+aHBnX3N0b3JlX2lkLCBsYWJlbCA9IH5ocGdfZ2VucmVfbmFtZSwKICAgICAgICAgICAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkpCmBgYAoKCkhlcmUgaXMgdGhlIGJyZWFrZG93biBvZiAqZ2VucmUqIGFuZCAqYXJlYSogZm9yIHRoZSAqaHBnKiByZXN0YXVyYW50czoKCmBgYHtyIHNwbGl0PUZBTFNFLCBmaWcuYWxpZ24gPSAnZGVmYXVsdCcsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLmNhcCA9IkZpZy4gOCIsIG91dC53aWR0aD0iMTAwJSJ9CnBsb3RfMSA8LSBocGdfc3RvcmUgJT4lCiAgZ3JvdXBfYnkoaHBnX2dlbnJlX25hbWUpICU+JQogIGNvdW50KCkgJT4lCiAgZ2dwbG90KGFlcyhyZW9yZGVyKGhwZ19nZW5yZV9uYW1lLCBuLCBGVU4gPSBtaW4pLCBuLCBmaWxsID0gaHBnX2dlbnJlX25hbWUpKSArCiAgZ2VvbV9jb2woKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBsYWJzKHggPSAiVHlwZSBvZiBjdWlzaW5lIiwgeSA9ICJOdW1iZXIgb2YgaHBnIHJlc3RhdXJhbnRzIikKCnBsb3RfMiA8LSBocGdfc3RvcmUgJT4lCiAgbXV0YXRlKGFyZWEgPSBzdHJfc3ViKGhwZ19hcmVhX25hbWUsIDEsIDIwKSkgJT4lCiAgZ3JvdXBfYnkoYXJlYSkgJT4lCiAgY291bnQoKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgdG9wX24oMTUsbikgJT4lCiAgZ2dwbG90KGFlcyhyZW9yZGVyKGFyZWEsIG4sIEZVTiA9IG1pbikgLG4sIGZpbGwgPSBhcmVhKSkgKwogIGdlb21fY29sKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gIlRvcCAxNSBhcmVhcyIsIHkgPSAiTnVtYmVyIG9mIGhwZyByZXN0YXVyYW50cyIpCgpsYXlvdXQgPC0gbWF0cml4KGMoMSwyKSwxLDIsYnlyb3c9VFJVRSkKbXVsdGlwbGVfcGxvdChwbG90XzEsIHBsb3RfMiwgbGF5b3V0PWxheW91dCkKYGBgCgpXZSBjYW4gc2VlLAoKMSkgV2hlbiBjb21wYXJlZCB3aXRoICJhaXIiIHJlc3RhdXJhbnRzLCAiSFBHIiBjb250YWlucyBtb3JlIHR5cGVzIG9mIHJlc3RhdXJhbnRzLCBhbmQgIkphcGFuZXNlIHN0eWxlIiBzZWVtcyB0byBjb250YWluIG1vcmUgc3BlY2lmaWMgY2F0ZWdvcmllcyBpbiAiYWlyIiBkYXRhLgoKMikgVG9reW8gYW5kIE9zYWthIGFnYWluIGZlYXR1cmUgcHJvbWluZW50bHkgaW4gdGhlIHRvcCAxNSBjaXRpZXMsIGFzIHdlIGZvdW5kIGluIHRoZSAiYWlyIiBkYXRhLgoKCiMgRmVhdHVyZSByZWxhdGlvbnMKCiMjIFZpc2l0b3JzIHBlciBnZW5yZQoKRm9yIHRoZSBmaXJzdCBvbmUsIHdlIHdpbGwgdXNlIHRoZSBtdWx0aS1mZWF0dXJlIHNwYWNlIGRpYWdyYW0gdG8gc3R1ZHkgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB0eXBlcyBvZiBjdWlzaW5lIGFuZCB0aGUgbnVtYmVyIG9mIHRvdXJpc3RzLgoKCgpgYGB7ciBzcGxpdD1GQUxTRSwgZmlnLmFsaWduID0gJ2RlZmF1bHQnLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5jYXAgPSJGaWcuIDExIiwgb3V0LndpZHRoPSIxMDAlIn0KCmZvbyA8LSBhaXJfdmlzaXRzICU+JQogIGxlZnRfam9pbihhaXJfc3RvcmUsIGJ5ID0gImFpcl9zdG9yZV9pZCIpCgpmb28gJT4lCiAgZ3JvdXBfYnkodmlzaXRfZGF0ZSwgYWlyX2dlbnJlX25hbWUpICU+JQogIHN1bW1hcmlzZShtZWFuX3Zpc2l0b3JzID0gbWVhbih2aXNpdG9ycykpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBnZ3Bsb3QoYWVzKHZpc2l0X2RhdGUsIG1lYW5fdmlzaXRvcnMsIGNvbG9yID0gYWlyX2dlbnJlX25hbWUpKSArCiAgZ2VvbV9saW5lKCkgKwogIGxhYnMoeSA9ICJBdmVyYWdlIG51bWJlciBvZiB2aXNpdG9ycyB0byAnYWlyJyByZXN0YXVyYW50cyIsIHggPSAiRGF0ZSIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBzY2FsZV95X2xvZzEwKCkgKwogIGZhY2V0X3dyYXAofiBhaXJfZ2VucmVfbmFtZSkKYGBgCgpXZSBjYW4gc2VlOgoKMSkgSW4gZ2VuZXJhbCwgdGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIGN1c3RvbWVycyBwZXIgZGF5IGZvciBlYWNoIHR5cGUgaXMgMTAgdG8gMTAwLiBTaW1pbGFybHksIGluIGVhY2ggY2F0ZWdvcnksIGxvbmctdGVybSB0cmVuZHMgbG9vayBmYWlybHkgc3RhYmxlLiBTaW5jZSB0aGUgZW5kIG9mIDIwMTYsIHRoZSBwb3B1bGFyaXR5IG9mICJjcmVhdGl2ZSBjdWlzaW5lIiBhbmQgIk9rb25vbWl5YWtpIiBoYXMgYmVlbiBvbiB0aGUgcmlzZSwgd2hpbGUgdGhlIHBvcHVsYXJpdHkgb2YgIkFzaWFuIGN1aXNpbmUiIGhhcyBiZWVuIG9uIHRoZSBkZWNsaW5lLgoKMikgQWx0aG91Z2ggIkFzaWFuIiByZXN0YXVyYW50cyBhcmUgcmFyZSwgdGhleSBzZWVtIHRvIGJlIHBvcHVsYXIuCgoKIyMgVGhlIGltcGFjdCBvZiB2YWNhdGlvbnMKCgpOb3cgbGV0J3Mgc3R1ZHkgdGhlIGVmZmVjdCBvZiB2YWNhdGlvbnMgb24gdmlzaXRvciBOdW1iZXJzOgoKYGBge3Igc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiAxMyIsIGZpZy5oZWlnaHQ9My41LCBvdXQud2lkdGg9IjEwMCUifQoKZm9vIDwtIGFpcl92aXNpdHMgJT4lCiAgbXV0YXRlKGNhbGVuZGFyX2RhdGUgPSBhcy5jaGFyYWN0ZXIodmlzaXRfZGF0ZSkpICU+JQogIGxlZnRfam9pbih2YWNhdGlvbnMsIGJ5ID0gImNhbGVuZGFyX2RhdGUiKQoKcGxvdF8xIDwtIGZvbyAlPiUKICBnZ3Bsb3QoYWVzKGhvbGlkYXlfZmxnLCB2aXNpdG9ycywgY29sb3IgPSBob2xpZGF5X2ZsZykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfeV9sb2cxMCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwbG90XzIgPC0gZm9vICU+JQogIG11dGF0ZSh3ZGF5ID0gd2RheShkYXRlLCBsYWJlbCA9IFRSVUUpKSAlPiUKICBncm91cF9ieSh3ZGF5LCBob2xpZGF5X2ZsZykgJT4lCiAgc3VtbWFyaXNlKG1lYW5fdmlzaXRvcnMgPSBtZWFuKHZpc2l0b3JzKSkgJT4lCiAgZ2dwbG90KGFlcyh3ZGF5LCBtZWFuX3Zpc2l0b3JzLCBjb2xvciA9IGhvbGlkYXlfZmxnKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDQpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBsYWJzKHkgPSAiQXZlcmFnZSBudW1iZXIgb2YgdmlzaXRvcnMiKQoKbGF5b3V0IDwtIG1hdHJpeChjKDEsMiksMSwyLGJ5cm93PVRSVUUpCm11bHRpcGxlX3Bsb3QocGxvdF8xLCBwbG90XzIsIGxheW91dD1sYXlvdXQpCmBgYAoKCgpXZSBjYW4gc2VlOgoKMSkgT3ZlcmFsbCwgdmFjYXRpb25zIGhhZCBubyBlZmZlY3Qgb24gYXZlcmFnZSB2aXNpdG9yIE51bWJlcnMgKGxlZnQgZ3JhcGgpLiAKCjIpIEFsdGhvdWdoIGhvbGlkYXkgb24gYSB3ZWVrZW5kIGhhcyBsaXR0bGUgdG8gbm8gaW1wYWN0IG9uIHRoZSB2aXNpdG9yIG51bWJlcnMuIEl0IGV2ZW4gZGVjcmVhc2VzIHRoZW0gc2xpZ2h0bHkuIFRoaXMgaXMgYW4gaW50ZXJlc3RpbmcgcGhlbm9tZW5vbi4gKHJpZ2h0IGdyYXBoKS4KCiMjIFJlc3RhdXJhbnRzIHBlciBhcmVhIGFuZCB0aGUgZWZmZWN0IG9uIHZpc2l0b3IgbnVtYmVycwoKSGVyZSBpcyBhIGludGVyZXN0aW5nIGlkZWEgdGhhdCB3ZSB3YW50IHRvIHN0dWR5IHdpdGgsCgpJZiB3ZSBoYWQgdGhlIG9ubHkgZ291cm1ldCBiYXIgaW4gdGhlIGFyZWEgYW5kIGl0IHdhcyBwb3B1bGFyLCB3ZSdkIGhhdmUgaHVuZHJlZHMgb2YgY3VzdG9tZXJzIGFuZCB3ZSB3b3VsZG4ndCB3b3JyeSBhYm91dCBsb3NpbmcgdGhlbS4gQnV0IGlmIHRoZXJlIGFyZSBtb3JlIHRoYW4gMTAgcmVzdGF1cmFudHMgb2YgdGhlIHNhbWUgdHlwZSBvbiB0aGlzIHN0cmVldCwgZXZlbiBpZiB3ZSBkbyBvdXIgYmVzdCwgc29tZSBjdXN0b21lcnMgd2lsbCBnbyBlbHNld2hlcmUuIFNvIGxldCdzIGxvb2sgYXQgdGhlIGltcGFjdCBvZiB0aGUgbnVtYmVyIG9mIHNwZWNpZmljIHJlc3RhdXJhbnQgdHlwZXMgaW4gZWFjaCByZWdpb24gb24gdGhlIG51bWJlciBvZiBjdXN0b21lcnMKCgpXZSBmaXJzdCBvdXRsaW5lZCB0aGUgZnJlcXVlbmN5IG9mIGVhY2ggcmVnaW9uIHNwZWNpZmljIHR5cGUgZm9yIHRoZSB0d28gZGF0YXNldHMgYWlyIGFuZCBIUEcgc3RvcmUuIFRoZSBzaXplIG9mIHRoZSBkb3QgaXMgcHJvcG9ydGlvbmFsIHRvIHRoZSBudW1iZXIgb2YgY2FzZXM6CgpgYGB7ciBzcGxpdD1GQUxTRSwgZmlnLmFsaWduID0gJ2RlZmF1bHQnLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5jYXAgPSJGaWcuIDE0Iiwgb3V0LndpZHRoPSIxMDAlIn0KCmFpcl9zdG9yZSAlPiUKICBtdXRhdGUoYXJlYSA9IHN0cl9zdWIoYWlyX2FyZWFfbmFtZSwgMSwgMTIpKSAlPiUKICBnZ3Bsb3QoYWVzKGFyZWEsIGFpcl9nZW5yZV9uYW1lKSkgKwogIGdlb21fY291bnQoY29sb3VyID0gImdyZWVuIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCBheGlzLnRleHQueCAgPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEsIHZqdXN0PTAuOSkpCgpgYGAKCldlIGNhbiBzZWU6CgoxKSBTb21lIGFyZWFzIGhhdmUgYSB3aWRlIHZhcmlldHkgb2YgcmVzdGF1cmFudHMsIHdoaWxlIG90aGVycyBoYXZlIG9ubHkgb25lIGFpciByZXN0YXVyYW50LgoKMikgU2ltaWxhcmx5LCBjdWlzaW5lcyBzdWNoIGFzIGl6YWtheWEgb3IgY2FmZSBhcmUgdmVyeSBjb21tb24sIHdoaWxlIG90aGVycyBjYW4gb25seSBiZSBmb3VuZCBpbiBhIGZldyBhcmVhcy4KCkdyYXBocyBvZiB0aGUgc2FtZSB0eXBlIGZvciBIUEcgZGF0YSBsb29rIHNpbWlsYXIsIGJ1dCBhcmUgYnVzaWVyIGJlY2F1c2Ugb2YgdGhlIGdyZWF0ZXIgbnVtYmVyIG9mIHR5cGVzLgoKYGBge3Igc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiAxNSIsIG91dC53aWR0aD0iMTAwJSJ9CgpocGdfc3RvcmUgJT4lCiAgbXV0YXRlKGFyZWEgPSBzdHJfc3ViKGhwZ19hcmVhX25hbWUsIDEsIDEwKSkgJT4lCiAgZ2dwbG90KGFlcyhhcmVhLCBocGdfZ2VucmVfbmFtZSkpICsKICBnZW9tX2NvdW50KGNvbG91ciA9ICJibHVlIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCBheGlzLnRleHQueCAgPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEsIHZqdXN0PTAuOSkpCgpgYGAKCgoKV2UgY2FuIHNlZSwKCjEpIFRoZXJlIGFyZSBhbHNvIGJ1c3kgYXJlYXMgd2l0aCBtYW55IHJlc3RhdXJhbnRzIGFuZCBhcmVhcyB3aXRoIG9ubHkgYSBmZXcgcmVzdGF1cmFudHMuCgoyKSBCb3RoICJKYXBhbmVzZSBjdWlzaW5lIiBhbmQgImludGVybmF0aW9uYWwgY3Vpc2luZSIgYXJlIGNvbW1vbiBhbmQgcG9wdWxhci4gIkVudGVydGFpbm1lbnQgYmFycyIgYW5kICJ1ZG9uL2J1Y2t3aGVhdCBub29kbGVzIiBhcmUgcmFyZSwgYXMgYXJlICJTaGFuZ2hhaSBjdWlzaW5lIiBhbmQgImRpbSBzdW0iLgoKCldlIHN0YXJ0IHdpdGggdGhlIGFpciBkYXRhOgoKYGBge3Igc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiAxNiIsIG91dC53aWR0aD0iMTAwJSJ9CgphaXJfc3RvcmUgJT4lCiAgZ3JvdXBfYnkoYWlyX2dlbnJlX25hbWUsIGFpcl9hcmVhX25hbWUpICU+JQogIGNvdW50KCkgJT4lCiAgZ2dwbG90KGFlcyhyZW9yZGVyKGFpcl9nZW5yZV9uYW1lLCBuLCBGVU4gPSBtZWFuKSwgbikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZ2VvbV9qaXR0ZXIoY29sb3IgPSAiYmx1ZSIpICsKICBzY2FsZV95X2xvZzEwKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gIkFpciBnZW5yZSIsIHkgPSAiT2NjdXJlbmNlcyBwZXIgYWlyIGFyZWEiKQpgYGAKCgoKV2UgY2FuIHNlZToKCjEpIE9ubHkgYSBmZXcgdHlwZXMgaGF2ZSBhIG1lZGlhbiBvZiBtb3JlIHRoYW4gdHdvIHJlc3RhdXJhbnRzIGluIG9uZSBhcmVhLiBGb3IgaW5zdGFuY2UsICJJdGFsaWFuL0ZyZW5jaCIgcmVzdGF1cmFudHMgb3IgIkJhci9Db2NrdGFpbCIgcGxhY2VzLCAgYXJlIGVhc2llciB0byBiZSBmb3VuZCB3aXRoIG1vcmUgdGhhbiB0d28gaW4gdGhlIHNhbWUgYXJlYS4KCjIpIEZvciBtb3N0IHR5cGVzLCB0aGUgZGlzdHJpYnV0aW9uIGlzIGZpcm1seSBjbHVzdGVyZWQgaW4gZWFjaCBhcmVhIG9mIDIgY2FzZXMgYW5kIHNjYXR0ZXJlZCB0b3dhcmQgaGlnaGVyIE51bWJlcnMuIFRoZSBudW1iZXIgb2YgImNhZmVzIiBpcyBoaWdoZXN0LCB3aXRoIDI2IGluIG9uZSBhcmVhLgoKMykgU3RyYW5nZWx5IGVub3VnaCwgdGhlIG1pbmltdW0gaGVyZSBpcyAyLCBub3QgMS4gVGhpcyBtZWFucyB0aGF0IG5vICJhaXIiIHJlc3RhdXJhbnQgaXMgdGhlIG9ubHkgb25lIGluIGFueSByZWdpb24uIAoKYGBge3J9CgphaXJfc3RvcmUgJT4lCiAgZmlsdGVyKGFpcl9zdG9yZV9pZCA9PSAiYWlyX2I1NTk4ZDEyZDFiODQ4OTAiIHwgYWlyX3N0b3JlX2lkID09ICJhaXJfYmJlMWMxYTQ3ZTA5ZjE2MSIpCgphaXJfdmlzaXRzICU+JQogIGZpbHRlcihhaXJfc3RvcmVfaWQgPT0gImFpcl9iNTU5OGQxMmQxYjg0ODkwIiB8IGFpcl9zdG9yZV9pZCA9PSAiYWlyX2JiZTFjMWE0N2UwOWYxNjEiKSAlPiUKICBhcnJhbmdlKHZpc2l0X2RhdGUpICU+JQogIGhlYWQoMTApCgpgYGAKCk5vdyB3ZSBsb29rIGF0IHRoZSBzYW1lIGRpc3RyaWJ1dGlvbiBmb3IgdGhlIEhQRyByZXN0YXVyYW50czoKCgoKYGBge3Igc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiAxNyIsIG91dC53aWR0aD0iMTAwJSJ9Cgpmb29iYXIgPC0gaHBnX3N0b3JlICU+JQogIGdyb3VwX2J5KGhwZ19nZW5yZV9uYW1lLCBocGdfYXJlYV9uYW1lKSAlPiUKICBjb3VudCgpCgpmb29iYXIgJT4lCiAgZ2dwbG90KGFlcyhyZW9yZGVyKGhwZ19nZW5yZV9uYW1lLCBuLCBGVU4gPSBtZWFuKSwgbikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZ2VvbV9qaXR0ZXIoY29sb3IgPSAicmVkIikgKwogIHNjYWxlX3lfbG9nMTAoKSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKHggPSAiaHBnIGdlbnJlIiwgeSA9ICJDYXNlcyBwZXIgaHBnIGFyZWEiKQpgYGAKCldlIGNhbiBzZWU6CgoxKSBIZXJlLCB3ZSBvYnZpb3VzbHkgaGF2ZSBhIG1pbiBvZiBvbmUgdHlwZSAgcGVyIHJlZ2lvbiwgYW5kIGJlY2F1c2Ugb2YgdGhlIGhpZ2ggb3ZlcmFsbCBudW1iZXIsIHRoZXJlIGlzIGFsc28gbW9yZSBkaXZlcnNpdHkgaW4gdGhlIG1lZGlhbiBjYXNlLgoKMilUaGUgbW9zdCBleHRyZW1lICJnZW5yZSIgaXMgIkphcGFuZXNlIHN0eWxlIiwgd2l0aCBhbiBhdmVyYWdlIG9mIG1vcmUgdGhhbiAxMCByZXN0YXVyYW50cyBwZXIgcmVnaW9uLiAKClVzaW5nIGluZm9ybWF0aW9uIGFib3V0IHRoZSBudW1iZXIgb2YgdHlwZXMgb2YgcmVzdHJhdW50cyBpbiBlYWNoIGFyZWEsIHdlIGNhbiBub3cgcXVhbnRpZnkgdGhlIGNsdXN0ZXJpbmcgb3IgImNyb3dkaW5nIiBvZiB0aGUgZGF0YXNldCBhbmQgY29ycmVsYXRlIGl0IHdpdGggdGhlIG51bWJlciBvZiB2aXNpdG9ycy4gVGhlIG5leHQgZmlndXJlIGZpcnN0IHNob3dzIHRoZSBvdmVyYWxsIGRpc3RyaWJ1dGlvbiBvZiBhaXIgYW5kIEhQRyBkYXRhIHBvaW50cyBpbiB0aGUgZmlyc3QgdHdvIGZpZ3VyZXMvCgpgYGB7ciBzcGxpdD1GQUxTRSwgZmlnLmFsaWduID0gJ2RlZmF1bHQnLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5jYXAgPSJGaWcuIDE4Iiwgb3V0LndpZHRoPSIxMDAlIn0KCmZvbyA8LSBhaXJfdmlzaXRzICU+JQogIGxlZnRfam9pbihhaXJfc3RvcmUsIGJ5ID0gImFpcl9zdG9yZV9pZCIpCgpiYXIgPC0gYWlyX3N0b3JlICU+JQogIGdyb3VwX2J5KGFpcl9nZW5yZV9uYW1lLCBhaXJfYXJlYV9uYW1lKSAlPiUKICBjb3VudCgpCgpmb29iYXIgPC0gaHBnX3N0b3JlICU+JQogIGdyb3VwX2J5KGhwZ19nZW5yZV9uYW1lLCBocGdfYXJlYV9uYW1lKSAlPiUKICBjb3VudCgpCgpwbG90XzEgPC0gYmFyICU+JQogIGdncGxvdChhZXMobikpICsKICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gImJsYWNrIiwgYmlud2lkdGggPSAxKSArCiAgbGFicyh4ID0gIkFpciBnZW5yZXMgcGVyIGFyZWEiKQoKcGxvdF8yIDwtIGZvb2JhciAlPiUKICBnZ3Bsb3QoYWVzKG4pKSArCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJyZWQiLCBiaW53aWR0aCA9IDEpICsKICBsYWJzKHggPSAiSFBHIGdlbnJlcyBwZXIgYXJlYSIpCgpwbG90XzMgPC0gZm9vICU+JQogIGdyb3VwX2J5KGFpcl9nZW5yZV9uYW1lLCBhaXJfYXJlYV9uYW1lKSAlPiUKICBzdW1tYXJpc2UobWVhbl9sb2dfdmlzaXQgPSBtZWFuKGxvZzFwKHZpc2l0b3JzKSkpICU+JQogIGxlZnRfam9pbihiYXIsIGJ5ID0gYygiYWlyX2dlbnJlX25hbWUiLCJhaXJfYXJlYV9uYW1lIikpICU+JQogIGdyb3VwX2J5KG4pICU+JQogIHN1bW1hcmlzZShtZWFuX21sdiA9IG1lYW4obWVhbl9sb2dfdmlzaXQpLAogICAgICAgICAgICBzZF9tbHYgPSBzZChtZWFuX2xvZ192aXNpdCkpICU+JQogIHJlcGxhY2VfbmEobGlzdChzZF9tbHYgPSAwKSkgJT4lCiAgZ2dwbG90KGFlcyhuLCBtZWFuX21sdikpICsKICBnZW9tX3BvaW50KGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDQpICsKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gbWVhbl9tbHYgLSBzZF9tbHYsIHltYXggPSBtZWFuX21sdiArIHNkX21sdiksIHdpZHRoID0gMC41LCBzaXplID0gMC43LCBjb2xvciA9ICJibHVlIikgKwogIGxhYnMoeCA9ICJDYXNlcyBvZiBpZGVudGljYWwgQWlyIGdlbnJlcyBwZXIgYXJlYSIsIHkgPSAiTWVhbiArLy0gU0Qgb2ZcbiBtZWFuIGxvZzFwIHZpc2l0b3JzIikKCmxheW91dCA8LSBtYXRyaXgoYygxLDIsMywzKSwyLDIsYnlyb3c9VFJVRSkKCm11bHRpcGxlX3Bsb3QocGxvdF8xLCBwbG90XzIsIHBsb3RfMywgbGF5b3V0PWxheW91dCkKCmBgYAoKCiMgRm9yZWNhc3RpbmcgbWV0aG9kcyBhbmQgZXhhbXBsZXMKCkZpbmFsbHksIHdlIHdpbGwgcnVuIG91ciB0aW1lIHNlcmllcyBwcmVkaWN0aW9uIG1vZGVscyBXZSBsZWFybmVkIGEgbG90IGFib3V0IGRhdGFzZXRzIGFuZCB0aGVpciBhdHRyaWJ1dGVzLiBUaGUgZm9sbG93aW5nIHNlY3Rpb25zIGRlc2NyaWJlIHRoZSBiYXNpYyBmb3JlY2FzdGluZyBtZXRob2RzLiAKCiMjIEFSSU1BIC8gYXV0by5hcmltYQoKQXV0b3JlZ3Jlc3NpdmUgaW50ZWdyYXRlZCBtb3ZpbmcgYXZlcmFnZSBtb2RlbCBpcyBhIHBvcHVsYXIgbWV0aG9kIGZvciBmb3JlY2FzdGluZy4gVGhpcyBtb2RlbCBjb25zaXN0cyBvZiB0aHJlZSBidWlsZGluZyBibG9ja3MsIHRocmVlIGluZGV4IHAsIGQsIHEgcGFyYW1ldGVyaXplZCBhcyBBUklNQShwLCBkLCBxKQoKSW4gdGhpcyBwcm9qZWN0LCB3ZSB3aWxsIGltcGxlbWVudCB0aGUgImF1dG8tYXJpbWEiIHRvb2wsIHRoYXQgZXN0aW1hdGVzIHRoZSBuZWNlc3NhcnkgYXJpbWEgcGFyYW1ldGVycyBmb3IgZWFjaCBpbmRpdmlkdWFsIHRpbWUgc2VyaWVzLgoKQWxzbywgd2UnbGwgaW1wbGVtZW50IHRoZSBuZWVkIHRvIHVzZSB0aGUgInRzIiB0b29sIHRvIGNvbnZlcnQgdGhlbSBpbnRvIHRpbWUgc2VyaWVzIG9iamVjdHMuIAoKV2UgdXNlIHRoZSBhaXJfc3RvcmVfaWQqICgiYWlyX2JhOTM3YmYxM2Q0MGZiMjQiKSBhcyBhbiBleGFtcGxlLgoKYGBge3J9CmFpcl9pZCA9ICJhaXJfYmE5MzdiZjEzZDQwZmIyNCIKYGBgCgpUbyB0ZXN0IG91ciBwcmVkaWN0aW9ucywgd2Ugd2lsbCBmb2xsb3cgdGhlIHNhbWUgdGltZSBmcmFtZSBhcyBvdXIgZmluYWwgdGFzayAoQXByaWwgMjMtbWF5IDMxKS4gSGVyZSwgd2UgYXV0b21hdGljYWxseSBleHRyYWN0IHRoZSAzOSBkYXlzIGZyb20gdGhlIGxlbmd0aCBvZiB0aGUgcHJlZGljdGVkIHJhbmdlIG9mICp0ZXN0KiBhbmQgZGVmaW5lIGl0IGFzIG91ciAicHJlZGljdGVkIGxlbmd0aCIuCgpgYGB7cn0KcHJlZF9sZW4gPC0gdGVzdCAlPiUKICBzZXBhcmF0ZShpZCwgYygiYWlyIiwgInN0b3JlX2lkIiwgImRhdGUiKSwgc2VwID0gIl8iKSAlPiUKICBkaXN0aW5jdChkYXRlKSAlPiUKICBucm93KCkKYGBgCgpXZSBzZWxlY3RlZCBhICJ0cmFpbmluZyIgc2FtcGxlIHRoYXQgcHJlZGljdGVkIHRoZSBmaW5hbCAzOSBkYXlzLiBXZSBjYWxjdWxhdGUgdGhlIHRvcCBlbmQgb2Ygb3VyIHRyYWluaW5nIGRhdGUgYW5kIHN1YnRyYWN0IG91ciAicHJlZGljdGVkIGxlbmd0aCIgZnJvbSB0aGlzIHZhbHVlIHRvIGRlZmluZSB0aGUgdmFsaWRhdGlvbiBzYW1wbGUgYXQgdGhlIGJlZ2lubmluZyBvZiBNYXJjaCAxNC4gV2UgYWxzbyBjcmVhdGVkIGEgZGF0YSBzZXQgdGhhdCBjb250YWlucyBhbGwgb2YgdmlzaXRfZGF0ZSB0byBwcmVwYXJlIGZvciBtYW55IHRpbWUgc2VyaWVzIHRoYXQgY29udGFpbiBnYXBzLgoKYGBge3J9ICAKbWF4X2RhdGUgPC0gbWF4KGFpcl92aXNpdHMkdmlzaXRfZGF0ZSkKc3BsaXRfZGF0ZSA8LSBtYXhfZGF0ZSAtIHByZWRfbGVuCmFsbF92aXNpdHMgPC0gdGliYmxlKHZpc2l0X2RhdGUgPSBzZXEobWluKGFpcl92aXNpdHMkdmlzaXRfZGF0ZSksIG1heChhaXJfdmlzaXRzJHZpc2l0X2RhdGUpLCAxKSkKYGBgCgoKTmV4dCwgd2UgZXh0cmFjdCB0aGUgdGltZSBzZXJpZXMgZm9yIGEgcGFydGljdWxhciBhaXJfc3RvcmVfaWQqLiAKCmBgYHtyfQpmb28gPC0gYWlyX3Zpc2l0cyAlPiUKICBmaWx0ZXIoYWlyX3N0b3JlX2lkID09IGFpcl9pZCkKCnZpc2l0cyA8LSBmb28gJT4lCiAgcmlnaHRfam9pbihhbGxfdmlzaXRzLCBieSA9ICJ2aXNpdF9kYXRlIikgJT4lCiAgbXV0YXRlKHZpc2l0b3JzID0gbG9nMXAodmlzaXRvcnMpKSAlPiUKICByZXBsYWNlX25hKGxpc3QodmlzaXRvcnMgPSBtZWRpYW4obG9nMXAoZm9vJHZpc2l0b3JzKSkpKSAlPiUKICByb3duYW1lc190b19jb2x1bW4oKQpgYGAKCk5vdywgd2UgZGl2aWRlIHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cy4KCmBgYHtyfQp2aXNpdHNfdHJhaW4gPC0gdmlzaXRzICU+JSBmaWx0ZXIodmlzaXRfZGF0ZSA8PSBzcGxpdF9kYXRlKQp2aXNpdHNfdmFsaWQgPC0gdmlzaXRzICU+JSBmaWx0ZXIodmlzaXRfZGF0ZSA+IHNwbGl0X2RhdGUpCmBgYAoKCk5vdyBjb21lcyB0aGUgZml0dGluZyBwYXJ0LgoKYGBge3J9CmFyaW1hLmZpdCA8LSBhdXRvLmFyaW1hKHRzY2xlYW4odHModmlzaXRzX3RyYWluJHZpc2l0b3JzLCBmcmVxdWVuY3kgPSA3KSksCiAgICAgICAgICAgICAgICAgICAgICAgIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKYGBgCgoKVXNpbmcgdGhlIGZpdHRlZCBBUklNQSBtb2RlbCwgd2Ugd2lsbCAiZm9yZWNhc3QiIG91ciAicHJlZGljdGVkIGxlbmd0aCIuIEF0IHRoaXMgcG9pbnQgd2UgaW5jbHVkZSB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbC4KCmBgYHtyfQphcmltYV92aXNpdHMgPC0gYXJpbWEuZml0ICU+JSBmb3JlY2FzdChoID0gcHJlZF9sZW4sIGxldmVsID0gYyg1MCw5NSkpCmBgYAoKCkZpbmFsbHksIHdlIHBsb3Qgb3VyIHByZWRpY3Rpb24uIAoKYGBge3Igc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiAzMSIsIG91dC53aWR0aD0iMTAwJSJ9CgphcmltYV92aXNpdHMgJT4lCiAgYXV0b3Bsb3QgKwogIGdlb21fbGluZShhZXMoYXMuaW50ZWdlcihyb3duYW1lKS83LCB2aXNpdG9ycyksIAogICAgICAgICAgICBkYXRhID0gdmlzaXRzX3ZhbGlkLCBjb2xvciA9ICJncmV5IikgKwogIGxhYnMoeCA9ICJUaW1lICIsIHkgPSAiIFZpc2l0b3JzIHZzIGF1dG8uYXJpbWEgcHJlZGljdGlvbnMiKQoKYGBgCgpXZSBmb3VuZCB0aGF0IHRoZSBwcmVkaWN0aW9ucyBmcm9tIHRoZSBmaXJzdCBmZXcgZGF5cyB3ZXJlIHZlcnkgY29uc2lzdGVudC4KCiMjIFByb3BoZXQKClRoZSBQcm9waGV0IGZvcmVjYXN0aW5nIHRvb2wgaXMgYW4gb3Blbi1zb3VyY2Ugc29mdHdhcmUgcmVsZWFzZWQgYnkgRmFjZWJvb2sncyBjb3JlIGRhdGEgc2NpZW5jZSB0ZWFtLiBJdCBpcyBhIHVzZWZ1bCB0b29sIGZvciBib3RoIFIgYW5kIFB5dGhvbi4KCkxldCdzIGxvb2sgYXQgdGhpcyB0b29sIHN0ZXAgYnkgc3RlcCBhZ2Fpbi4gV2Ugd2lsbCBidWlsZCBvbiB0aGUgd29yayBvZiB0aGUgQVJJTUEgc2VjdGlvbiBhbmQgd2lsbCBub3QgcmVwZWF0IGFueSBvZiB0aGUgZXhwbGFuYXRpb25zIHRoYXQgY2FuIGJlIGZvdW5kIGVhcmxpZXIuCgpXZSB3aWxsIGFnYWluIGNyZWF0ZSBhIHNldCBvZiB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBmb3IgdGhlIHNhbWUgdGltZSBwZXJpb2QgYXMgYWJvdmUuIFRoZSBvbmx5IGRpZmZlcmVuY2UgaW4gb3VyIEFSSU1BIGFwcHJvYWNoIGlzOiAKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0KCmFpcl9pZCA9ICJhaXJfYmE5MzdiZjEzZDQwZmIyNCIKCnByZWRfbGVuIDwtIHRlc3QgJT4lCiAgc2VwYXJhdGUoaWQsIGMoImFpciIsICJzdG9yZV9pZCIsICJkYXRlIiksIHNlcCA9ICJfIikgJT4lCiAgZGlzdGluY3QoZGF0ZSkgJT4lCiAgbnJvdygpCgptYXhfZGF0ZSA8LSBtYXgoYWlyX3Zpc2l0cyR2aXNpdF9kYXRlKQpzcGxpdF9kYXRlIDwtIG1heF9kYXRlIC0gcHJlZF9sZW4KYWxsX3Zpc2l0cyA8LSB0aWJibGUodmlzaXRfZGF0ZSA9IHNlcShtaW4oYWlyX3Zpc2l0cyR2aXNpdF9kYXRlKSwgbWF4KGFpcl92aXNpdHMkdmlzaXRfZGF0ZSksIDEpKQoKZm9vIDwtIGFpcl92aXNpdHMgJT4lCiAgZmlsdGVyKGFpcl9zdG9yZV9pZCA9PSBhaXJfaWQpCgp2aXNpdHMgPC0gZm9vICU+JQogIHJpZ2h0X2pvaW4oYWxsX3Zpc2l0cywgYnkgPSAidmlzaXRfZGF0ZSIpICU+JQogIG11dGF0ZSh2aXNpdG9ycyA9IGxvZzFwKHZpc2l0b3JzKSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCkgJT4lCiAgc2VsZWN0KHkgPSB2aXNpdG9ycywKICAgICAgICAgZHMgPSB2aXNpdF9kYXRlKQoKdmlzaXRzX3RyYWluIDwtIHZpc2l0cyAlPiUgZmlsdGVyKGRzIDw9IHNwbGl0X2RhdGUpCnZpc2l0c192YWxpZCA8LSB2aXNpdHMgJT4lIGZpbHRlcihkcyA+IHNwbGl0X2RhdGUpCmBgYAoKCkhlcmUgd2UgZml0IHRoZSBwcm9waGV0IG1vZGVsIGFuZCBtYWtlIHRoZSBmb3JlY2FzdDoKCmBgYHtyfQpwcm9waCA8LSBwcm9waGV0KHZpc2l0c190cmFpbiwgY2hhbmdlcG9pbnQucHJpb3Iuc2NhbGU9MC41LCB5ZWFybHkuc2Vhc29uYWxpdHk9RkFMU0UpCmZ1dHVyZSA8LSBtYWtlX2Z1dHVyZV9kYXRhZnJhbWUocHJvcGgsIHBlcmlvZHMgPSBwcmVkX2xlbikKZmNhc3QgPC0gcHJlZGljdChwcm9waCwgZnV0dXJlKQpgYGAKCgpUaGlzIGlzIHRoZSBwcm9waGV0IGZvcmVjYXN0IHBsb3Q6CgpgYGB7ciBmaWcuYWxpZ24gPSAnZGVmYXVsdCcsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLmNhcCA9IkZpZy4gMzIiLCBvdXQud2lkdGg9IjEwMCUifQpwbG90KHByb3BoLCBmY2FzdCkKYGBgCgpPYnNlcnZhdGlvbnMgYXJlIHJlcHJlc2VudGVkIGJ5IGJsYWNrIGRvdHMsIGZpdHRpbmcgbW9kZWxzIGFuZCBwcmVkaWN0aW9ucyBieSBibHVlIGxpbmVzLiBJbiB0aGUgbGlnaHQgYmx1ZSwgd2Ugc2VlIGNvcnJlc3BvbmRpbmcgdW5jZXJ0YWludHkuCgpQcm9waGV0IHByb3ZpZGVzIGEgYnJlYWtkb3duIGdyYXBoIHdoZXJlIHdlIGNoZWNrIGZvciBhZGRpdGlvbmFsIGNvbXBvbmVudHMgdG8gdGhlIG1vZGVsOiB0cmVuZHMsIGFubnVhbCBzZWFzb25hbGl0eSAoaWYgYW55KSwgYW5kIHdlZWtseSBjeWNsZXM6CgoKCmBgYHtyIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiAzMyIsIG91dC53aWR0aD0iMTAwJSJ9CnByb3BoZXRfcGxvdF9jb21wb25lbnRzKHByb3BoLCBmY2FzdCkKYGBgCgoKCkFzLCBXZSBjYW4gc2VlOgoKMSkgVGhlIHdlZWtseSBjaGFuZ2UgcGF0dGVybiBkZXRlY3RlZCBieSB3ZXZpbiBpcyBzaW1pbGFyIHRvIHdoYXQgd2UgZm91bmQgYmVmb3JlLiBGb3IgZXhhbXBsZSwgRnJpZGF5L1NhdHVyZGF5IGhhcyBtb3JlIGN1c3RvbWVycyB0aGFuIGFueSBvdGhlciB0aW1lIG9mIHRoZSB3ZWVrLiBCdXQsIHRoZSBkaWZmZXJlbmNlIGlzLHRoZSBhdmVyYWdlIG51bWJlciBvZiB2aXNpdG9ycyB0byBTdW4gaXMgbG93ZXIgdGhhbiBhdCBhbnkgb3RoZXIgdGltZS4gCgoyKSBCdXQgdGhlIGxvbmctdGVybSB0cmVuZCBpcyBkaWZmZXJlbnQgZnJvbSB3aGF0IHdlJ3ZlIHNlZW4gYmVmb3JlLiBUaGUgcHJldmlvdXMgYXZlcmFnZSBiZWhhdmlvciB3YXMgbW9yZSBsaWtlbHkgdG8gaGF2ZSByaXNlbiBhcm91bmQgRGVjZW1iZXIgMjAxNiwgYnV0IGluIHRoaXMgY2FzZSwgaXQgYXBwZWFycyB0byBoYXZlIG9jY3VycmVkIGluIG1pZC0yMDE2LgoKCiMjIFdlYXRoZXIgdGltZSBzZXJpZXMKCldlIHdpbGwgbm93IGxvb2sgYXQgYW4gZXhhbXBsZSBvZiB0aGUgd2VhdGhlciBkYXRhIGZyb20gYSBKYXBhbiBTdGF0aW9uLiBXZSdsbCBzZWxlY3QgdGhlIHN0YXRpb24gYHRva3lvX190b2t5by1rYW5hX190b25va3lvLmNzdmAgYXMgaXMgaGFzIGRhdGEgZm9yIG1vc3QgZmVhdHVyZXMgYnV0IG5vdCBhbGwuIFJlbWVtYmVyIHRoYXQgdGhlIGluZGl2aWR1YWwgd2VhdGhlciBkYXRhIGZpbGVzIGFyZSBpbiB0aGUgZm9sZGVyIGAxLTEtMTZfNS0zMS0xN19XZWF0aGVyYC4KCgpgYGB7cn0Kd2VhdGhlcl9kYXRhIDwtIGFzLnRpYmJsZShmcmVhZChzdHJfYyh3ZHBhdGgsInRva3lvX190b2t5by1rYW5hX190b25va3lvLmNzdiIpKSkKc3VtbWFyeSh3ZWF0aGVyX2RhdGEpCmdsaW1wc2Uod2VhdGhlcl9kYXRhKQpgYGAKCkFzLCB3ZSBjYW4gc2VlOgoKMSkgT3VyIHRyYWluIGFuZCB0ZXN0IHNldHMgYXJlIGNvdmVyZWQgYnkgZGFpbHkgZGF0YSBmb3IgdGhlIDUxNyBkYXlzLiBUaGVzZSBkYXRhIGluY2x1ZGUgaW5mb3JtYXRpb24gc3VjaCBhcyB0ZW1wZXJhdHVyZSwgcmFpbiwgc25vdywgYWlyIHByZXNzdXJlLCBhbmQgZXZlbiBjbG91ZCBjb3ZlciBvciBzdW5saWdodCBob3Vycy4KCjIpIFdlIHNlZSB0aGF0IHNvbWUgZmVhdHVyZXMgaW5jbHVkZSBtb3N0bHkgTkEuIEluIGZhY3QsIHRoaXMgc3BlY2lmaWMgd2VhdGhlciBzdGF0aW9uIGRhdGEgc2V0IGlzIG9uZSBvZiB0aGUgbW9yZSBjb21wbGV0ZSBvbmVzIGFuZCB5b3Ugd2lsbCBmaW5kIG1hbnkgbW9yZSBtaXNzaW5nIHZhbHVlcyBpbiBvdGhlciBzdGF0aW9ucy4gU29tZSBzdGF0aW9ucyBhcHBlYXIgdG8gaGF2ZSBlc3NlbnRpYWxseSBjb21wbGV0ZSBmZWF0dXJlIGRhdGEuIFRoaXMgbWFrZXMgaXQgbmVjZXNzYXJ5IHRvIGZvY3VzIG9uIHRoZSBvdmVyYWxsIGNvbW1vbiBmZWF0dXJlcyBpbiBhIGZpcnN0IG1vZGVsbGluZyBhcHByb2FjaC4KCldlIHdpbGwgZG8gYSBsaXR0bGUgYml0IG9mIGZvcm1hdHRpbmcgdG8gYWRkIHNvbWUgZGF0ZSBmZWF0dXJlcyBsaWtlIG1vbnRoIG9yIGRheSBvZiB0aGUgd2VlazoKCmBgYHtyfQp3ZWF0aGVyX2RhdGEgPC0gd2VhdGhlcl9kYXRhICU+JQogIG11dGF0ZShkYXRlID0geW1kKGNhbGVuZGFyX2RhdGUpLAogICAgICAgICB3ZGF5ID0gd2RheShkYXRlLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBUUlVFKSwKICAgICAgICAgbW9udGggPSBtb250aChkYXRlLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBUUlVFKSwKICAgICAgICAgd2VlayA9IHdlZWsoZGF0ZSkpICU+JQogIHNlbGVjdCgtY2FsZW5kYXJfZGF0ZSkKYGBgCgpUaGUgbW9udGhseSBzdGF0aXN0aWNzIGZvciBoaWdoIHRlbXBlcmF0dXJlIGFuZCBhdmVyYWdlIGh1bWlkaXR5LiAKCmBgYHtyIHNwbGl0PUZBTFNFLCBmaWcuYWxpZ24gPSAnZGVmYXVsdCcsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLmNhcCA9IkZpZy4gNDEiLCBvdXQud2lkdGg9IjEwMCUifQpwMSA8LSB3ZWF0aGVyX2RhdGEgJT4lCiAgZ2dwbG90KGFlcyh4ID0gaGlnaF90ZW1wZXJhdHVyZSwgeSA9IGZjdF9yZXYobW9udGgpLCBmaWxsID0gLi54Li4pKSArCiAgZ2VvbV9kZW5zaXR5X3JpZGdlc19ncmFkaWVudChzY2FsZSA9IDMsIHJlbF9taW5faGVpZ2h0ID0gMC4wMSwgZ3JhZGllbnRfbHdkID0gMS4sIGJhbmR3aWR0aCA9IDEuNCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIlRfbWF4IFvCsENdIiwgb3B0aW9uID0gIkMiKSArCiAgZ2d0aXRsZSgiTWF4aW11bSB0ZW1wZXJhdHVyZSBhdCBzdGF0aW9uIFxudG9reW8gdG9reW8ta2FuYSB0b25va3lvIGluIDIwMTYvMTciKSArCiAgbGFicyh4ID0gIkhpZ2ggdGVtcGVyYXR1cmUiLCB5ID0gIiIpICsKICB0aGVtZV9yaWRnZXMoZm9udF9zaXplID0gMTMsIGdyaWQgPSBUUlVFKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKQoKcDIgPC0gd2VhdGhlcl9kYXRhICU+JQogIGdncGxvdChhZXMoeCA9IGF2Z19odW1pZGl0eSwgeSA9IGZjdF9yZXYobW9udGgpLCBmaWxsID0gLi54Li4pKSArCiAgZ2VvbV9kZW5zaXR5X3JpZGdlc19ncmFkaWVudChzY2FsZSA9IDMsIHJlbF9taW5faGVpZ2h0ID0gMC4wMSwgZ3JhZGllbnRfbHdkID0gMS4sIGJhbmR3aWR0aCA9IDQpICsKICBzY2FsZV9maWxsX2NvbnRpbnVvdXMobG93ID0gIndoaXRlIiwgaGlnaCA9ICJkYXJrIGJsdWUiKSArCiAgZ2d0aXRsZSgiQXZlcmFnZSBodW1pZGl0eSBhdCBzdGF0aW9uIFxudG9reW8gdG9reW8ta2FuYSB0b25va3lvIGluIDIwMTYvMTciKSArCiAgbGFicyh4ID0gIkh1bWlkaXR5IiwgeSA9ICIiLCBmaWxsID0gIkh1bWlkaXR5IikgKwogIHRoZW1lX3JpZGdlcyhmb250X3NpemUgPSAxMywgZ3JpZCA9IFRSVUUpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpCgpsYXlvdXQgPC0gbWF0cml4KGMoMSwyKSwxLDIsYnlyb3c9VFJVRSkKbXVsdGlwbG90KHAxLCBwMiwgbGF5b3V0PWxheW91dCkKCmBgYAoKCldoYXQgd2UgZmluZCB0aGF0IHN1bW1lcnMgaW4gVG9reW8gYXJlIHF1aXRlIGEgYml0IGhvdHRlciBidXQgYWxzbyBtb3JlIGh1bWlkLiBUaGVyZSBpcyBhIHJhbmdlIG9mIGFib3V0IDIwIGRlZ3JlZXMgY2Vsc2l1cyBiZXR3ZWVuIHdpbnRlciBhbmQgc3VtbWVyLCBhbmQgYSBzcHJlYWQgb2Ygd2hhdCBsb29rcyBsaWtlIGFyb3VuZCAxMCBkZWdyZWVzIHdpdGhpbiBhIHR5cGljYWwgbW9udGguIFRoZSBodW1pZGl0eSBoYXMgbW9yZSB2YXJpYW5jZSBwZXIgbW9udGggYnV0IHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gd2ludGVyIGFuZCBzdW1tZXIgaXMgc3RpbGwgc2lnbmlmaWNhbnQuIAoKCk90aGVyIHdlYXRoZXIgaW5mb3JtYXRpb24gaW5jbHVkZSB0aGUgcHJlY2lwaXRhdGlvbiwgdG90YWwgYW5kIGRlZXBlc3Qgc25vd2ZhbGwgKGFsbCB0aHJlZSBtb3N0bHkgbWlzc2luZyBmb3IgdGhpcyBzdGF0aW9uKSwgaG91cnMgb2Ygc3VubGlnaHQsIGF2ZXJhZ2Ugd2luZCBzcGVlZCwgdmFyaW91cyBwcmVzc3VyZXMsIGNsb3VkIGNvdmVyLCBhbmQgc29sYXIgcmFkaWF0aW9uLiBIZXJlIHdlIHBsb3Qgc29tZSBvZiB0aG9zZSBmZWF0dXJlcyBvdmVyIHRoZSBtb250aHMgb2YgdGhlIHllYXI6CgpgYGB7ciAgc3BsaXQ9RkFMU0UsIGZpZy5hbGlnbiA9ICdkZWZhdWx0Jywgd2FybmluZyA9IEZBTFNFLCBmaWcuY2FwID0iRmlnLiA0MiIsIG91dC53aWR0aD0iMTAwJSJ9CgpwMSA8LSB3ZWF0aGVyX2RhdGEgJT4lCiAgZ2dwbG90KGFlcyhmY3RfcmV2KG1vbnRoKSwgaG91cnNfc3VubGlnaHQsIGZpbGwgPSBmY3RfcmV2KG1vbnRoKSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBsYWJzKHggPSAiTW9udGgiKQoKcDIgPC0gd2VhdGhlcl9kYXRhICU+JQogIGdncGxvdChhZXMoZmN0X3Jldihtb250aCksIGNsb3VkX2NvdmVyLCBmaWxsID0gZmN0X3Jldihtb250aCkpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgbGFicyh4ID0gIk1vbnRoIikKCnAzIDwtIHdlYXRoZXJfZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKGZjdF9yZXYobW9udGgpLCBwcmVjaXBpdGF0aW9uLCBmaWxsID0gZmN0X3Jldihtb250aCkpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgc2NhbGVfeV9sb2cxMCgpICsKICBsYWJzKHggPSAiTW9udGgiKQoKcDQgPC0gd2VhdGhlcl9kYXRhICU+JQogIGdncGxvdChhZXMoZmN0X3Jldihtb250aCksIGF2Z19sb2NhbF9wcmVzc3VyZSwgZmlsbCA9IGZjdF9yZXYobW9udGgpKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIHNjYWxlX3lfbG9nMTAoKSArCiAgbGFicyh4ID0gIk1vbnRoIikKCmxheW91dCA8LSBtYXRyaXgoYygxLDIsMyw0KSwyLDIsYnlyb3c9VFJVRSkKbXVsdGlwbG90KHAxLCBwMiwgcDMsIHA0LCBsYXlvdXQ9bGF5b3V0KQoKYGBgCgoKCkFzLCBXZSBjYW4gc2VlOgoKMSkgVGhlcmUgYXJlIGxlc3MgbnVtYmVyIG9mIHN1bmxpZ2h0IGhvdXJzIGluIHRoZSBtb250aCBvZlNlcCB0aGFuIGluIERlYywgZGVzcGl0ZSB0aGUgc2lnbmlmaWNhbnRseSBzaG9ydGVyIGRheXMuIFRoaXMgY291bGQgYmUgZXhwbGFpbmVkIGJ5IHRoZSBjbG91ZCBjb3ZlciBwbG90IHRoYXQgc2hvd3MgdGhhdCB3aW50ZXIgaXMgY29uc2lkZXJhYmx5IGxlc3MgY2xvdWR5IHRoYW4gdGhlIHJlc3Qgb2YgdGhlIHllYXIuIFRoZSBjbG91ZCBjb3ZlciBhcHBlYXJzIHRvIGJlIG1lYXN1cmVkIG9uIGEgcmVsYXRpdmUgc2NhbGUgYmV0d2VlbiAwIGFuZCAxMC4KCjIpIFRoZSBvdmVyYWxsIHByZWNpcGl0YXRpb24gc2VlbXMgbG93ZXN0IGluIEZlYjsgYWx0aG91Z2ggdGhlcmUgaXMgYSBsYXJnZSB2YXJpYW5jZSB3aXRoaW4gYSBtb250aCBhbmQgYWxsIGJveGVzIGFyZSBjb25zaXN0ZW50LiBSZW1lbWJlciB0aGF0IHRoZSBwcmVjaXBpdGF0aW9uIGZlYXR1cmUgaGFzIG1hbnkgbWlzc2luZyB2YWx1ZXMuCgozKVRoZSBhdmVyYWdlIGxvY2FsIHByZXNzdXJlIGNsZWFybHkgZHJvcHMgZHVyaW5nIHRoZSBzdW1tZXIsIHdpdGggQXVnIGhhdmluZyB0aGUgbG93ZXN0IHByZXNzdXJlIHZhbHVlcy4gVGhpcyBpcyBjb25zaXN0ZW50IHdpdGggdGhlIHRlbmRlbmN5IGZvciBoaWdoZXIgcHJlY2lwaXRhdGlvbiBkdXJpbmcgdGhhdCBtb250aC4KCldFIHBsYW5uZWQgdG8gZG9pYmcgc29tZSBmdXJ0aGVyIGFuYXlsc2lzIG9uIHdlYXRoZXIgZGF0YS4KCk5vdyB3ZSBhcmUgZ29pbmcgdG8gZG8gdGhlIHVuc3VwZXJ2aXNlZCBsZWFybmluZyBmb3Igd2VhdGhlcl9kYXRhLiAKCmBgYHtyfQpCaWdDaXRpZXMgPC0gd2VhdGhlcl9kYXRhICU+JQogIGFycmFuZ2UoZGVzYyhhdmdfdGVtcGVyYXR1cmUpKSAlPiUKICBoZWFkKDQwMDApICU+JQogIHNlbGVjdChoaWdoX3RlbXBlcmF0dXJlLCBsb3dfdGVtcGVyYXR1cmUpCmNpdHlfY2x1c3RzIDwtIEJpZ0NpdGllcyAlPiUKICBrbWVhbnMoY2VudGVycyA9IDkpICU+JQogIGZpdHRlZCgiY2xhc3NlcyIpICU+JQogIGFzLmNoYXJhY3RlcigpCkJpZ0NpdGllcyA8LSBCaWdDaXRpZXMgJT4lIG11dGF0ZShjbHVzdGVyID0gY2l0eV9jbHVzdHMpCkJpZ0NpdGllcyAlPiUgZ2dwbG90KGFlcyh4ID0gbG93X3RlbXBlcmF0dXJlLCB5ID0gaGlnaF90ZW1wZXJhdHVyZSkpICsKZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjbHVzdGVyKSwgYWxwaGEgPSAwLjUpCmBgYAoKTm93LCB3ZSBhcmUgZ29pbmcgdGhlIHNwcmVhZCB3ZWF0aGVyX3N0YXRpb25zIGRhdGEgYnkgbG9uZ2l0dWRlIGFuZCBsYXRpdHVkZS4gCmBgYHtyfQp3ZWF0aGVyX3N0YXRpb25zMTwtCiAgd2VhdGhlcl9zdGF0aW9ucyU+JQogIHNwcmVhZChsb25naXR1ZGUsbGF0aXR1ZGUpCndlYXRoZXJfc3RhdGlvbnMxJT4lCiAgaGVhZCg2KQpgYGAKCmBgYHtyfQphcHBseSh3ZWF0aGVyX2RhdGEsMixjbGFzcykKYGBgCgpGcm9tIHRoaXMgb25lIHdlIGtub3cgdGhlIHR5cGVzIG9mIGV2ZXJ5IHZhcmlhYmxlcyBpbiB0aGlzIGRhdGEgc2V0LiAKCmBgYHtyfQpzaW1fbWVhbiA8LSBzYXBwbHkoMToxMDAsIGZ1bmN0aW9uKHgpIHsKICBpZHggPC0gc2FtcGxlKDE6bnJvdyh3ZWF0aGVyX2RhdGEpLHNpemUgPSAwLjgqbnJvdyh3ZWF0aGVyX2RhdGEpLHJlcGxhY2UgPSBUUlVFKQogIG1lYW4od2VhdGhlcl9kYXRhJGF2Z190ZW1wZXJhdHVyZVtpZHhdKQp9KQpzaW1fbWVhbgpgYGAKCg==